普通文本  |  618行  |  16.34 KB

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/gpu/shader_disk_cache.h"

#include "base/threading/thread_checker.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/public/browser/browser_thread.h"
#include "gpu/command_buffer/common/constants.h"
#include "net/base/cache_type.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"

namespace content {

namespace {

static const base::FilePath::CharType kGpuCachePath[] =
    FILE_PATH_LITERAL("GPUCache");

void EntryCloser(disk_cache::Entry* entry) {
  entry->Close();
}

}  // namespace

// ShaderDiskCacheEntry handles the work of caching/updating the cached
// shaders.
class ShaderDiskCacheEntry
    : public base::ThreadChecker,
      public base::RefCounted<ShaderDiskCacheEntry> {
 public:
  ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,
                       const std::string& key,
                       const std::string& shader);
  void Cache();

 private:
  friend class base::RefCounted<ShaderDiskCacheEntry>;

  enum OpType {
    TERMINATE,
    OPEN_ENTRY,
    WRITE_DATA,
    CREATE_ENTRY,
  };

  ~ShaderDiskCacheEntry();

  void OnOpComplete(int rv);

  int OpenCallback(int rv);
  int WriteCallback(int rv);
  int IOComplete(int rv);

  base::WeakPtr<ShaderDiskCache> cache_;
  OpType op_type_;
  std::string key_;
  std::string shader_;
  disk_cache::Entry* entry_;

  DISALLOW_COPY_AND_ASSIGN(ShaderDiskCacheEntry);
};

// ShaderDiskReadHelper is used to load all of the cached shaders from the
// disk cache and send to the memory cache.
class ShaderDiskReadHelper
    : public base::ThreadChecker,
      public base::RefCounted<ShaderDiskReadHelper> {
 public:
  ShaderDiskReadHelper(base::WeakPtr<ShaderDiskCache> cache, int host_id);
  void LoadCache();

 private:
  friend class base::RefCounted<ShaderDiskReadHelper>;

  enum OpType {
    TERMINATE,
    OPEN_NEXT,
    OPEN_NEXT_COMPLETE,
    READ_COMPLETE,
    ITERATION_FINISHED
  };


  ~ShaderDiskReadHelper();

  void OnOpComplete(int rv);

  int OpenNextEntry();
  int OpenNextEntryComplete(int rv);
  int ReadComplete(int rv);
  int IterationComplete(int rv);

  base::WeakPtr<ShaderDiskCache> cache_;
  OpType op_type_;
  void* iter_;
  scoped_refptr<net::IOBufferWithSize> buf_;
  int host_id_;
  disk_cache::Entry* entry_;

  DISALLOW_COPY_AND_ASSIGN(ShaderDiskReadHelper);
};

class ShaderClearHelper
    : public base::RefCounted<ShaderClearHelper>,
      public base::SupportsWeakPtr<ShaderClearHelper> {
 public:
  ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,
                    const base::FilePath& path,
                    const base::Time& delete_begin,
                    const base::Time& delete_end,
                    const base::Closure& callback);
  void Clear();

 private:
  friend class base::RefCounted<ShaderClearHelper>;

  enum OpType {
    TERMINATE,
    VERIFY_CACHE_SETUP,
    DELETE_CACHE
  };

  ~ShaderClearHelper();

  void DoClearShaderCache(int rv);

  scoped_refptr<ShaderDiskCache> cache_;
  OpType op_type_;
  base::FilePath path_;
  base::Time delete_begin_;
  base::Time delete_end_;
  base::Closure callback_;

  DISALLOW_COPY_AND_ASSIGN(ShaderClearHelper);
};

ShaderDiskCacheEntry::ShaderDiskCacheEntry(base::WeakPtr<ShaderDiskCache> cache,
                                           const std::string& key,
                                           const std::string& shader)
  : cache_(cache),
    op_type_(OPEN_ENTRY),
    key_(key),
    shader_(shader),
    entry_(NULL) {
}

ShaderDiskCacheEntry::~ShaderDiskCacheEntry() {
  if (entry_)
    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                            base::Bind(&EntryCloser, entry_));
}

void ShaderDiskCacheEntry::Cache() {
  DCHECK(CalledOnValidThread());
  if (!cache_.get())
    return;

  int rv = cache_->backend()->OpenEntry(
      key_,
      &entry_,
      base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this));
  if (rv != net::ERR_IO_PENDING)
    OnOpComplete(rv);
}

void ShaderDiskCacheEntry::OnOpComplete(int rv) {
  DCHECK(CalledOnValidThread());
  if (!cache_.get())
    return;

  do {
    switch (op_type_) {
      case OPEN_ENTRY:
        rv = OpenCallback(rv);
        break;
      case CREATE_ENTRY:
        rv = WriteCallback(rv);
        break;
      case WRITE_DATA:
        rv = IOComplete(rv);
        break;
      case TERMINATE:
        rv = net::ERR_IO_PENDING;  // break the loop.
        break;
      default:
        NOTREACHED();  // Invalid op_type_ provided.
        break;
    }
  } while (rv != net::ERR_IO_PENDING);
}

int ShaderDiskCacheEntry::OpenCallback(int rv) {
  DCHECK(CalledOnValidThread());
  // Called through OnOpComplete, so we know |cache_| is valid.
  if (rv == net::OK) {
    cache_->backend()->OnExternalCacheHit(key_);
    cache_->EntryComplete(this);
    op_type_ = TERMINATE;
    return rv;
  }

  op_type_ = CREATE_ENTRY;
  return cache_->backend()->CreateEntry(
      key_,
      &entry_,
      base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this));
}

int ShaderDiskCacheEntry::WriteCallback(int rv) {
  DCHECK(CalledOnValidThread());
  // Called through OnOpComplete, so we know |cache_| is valid.
  if (rv != net::OK) {
    LOG(ERROR) << "Failed to create shader cache entry: " << rv;
    cache_->EntryComplete(this);
    op_type_ = TERMINATE;
    return rv;
  }

  op_type_ = WRITE_DATA;
  scoped_refptr<net::StringIOBuffer> io_buf = new net::StringIOBuffer(shader_);
  return entry_->WriteData(
      1,
      0,
      io_buf.get(),
      shader_.length(),
      base::Bind(&ShaderDiskCacheEntry::OnOpComplete, this),
      false);
}

int ShaderDiskCacheEntry::IOComplete(int rv) {
  DCHECK(CalledOnValidThread());
  // Called through OnOpComplete, so we know |cache_| is valid.
  cache_->EntryComplete(this);
  op_type_ = TERMINATE;
  return rv;
}

ShaderDiskReadHelper::ShaderDiskReadHelper(
    base::WeakPtr<ShaderDiskCache> cache,
    int host_id)
    : cache_(cache),
      op_type_(OPEN_NEXT),
      iter_(NULL),
      buf_(NULL),
      host_id_(host_id),
      entry_(NULL) {
}

void ShaderDiskReadHelper::LoadCache() {
  DCHECK(CalledOnValidThread());
  if (!cache_.get())
    return;
  OnOpComplete(net::OK);
}

void ShaderDiskReadHelper::OnOpComplete(int rv) {
  DCHECK(CalledOnValidThread());
  if (!cache_.get())
    return;

  do {
    switch (op_type_) {
      case OPEN_NEXT:
        rv = OpenNextEntry();
        break;
      case OPEN_NEXT_COMPLETE:
        rv = OpenNextEntryComplete(rv);
        break;
      case READ_COMPLETE:
        rv = ReadComplete(rv);
        break;
      case ITERATION_FINISHED:
        rv = IterationComplete(rv);
        break;
      case TERMINATE:
        cache_->ReadComplete();
        rv = net::ERR_IO_PENDING;  // break the loop
        break;
      default:
        NOTREACHED();  // Invalid state for read helper
        rv = net::ERR_FAILED;
        break;
    }
  } while (rv != net::ERR_IO_PENDING);
}

int ShaderDiskReadHelper::OpenNextEntry() {
  DCHECK(CalledOnValidThread());
  // Called through OnOpComplete, so we know |cache_| is valid.
  op_type_ = OPEN_NEXT_COMPLETE;
  return cache_->backend()->OpenNextEntry(
      &iter_,
      &entry_,
      base::Bind(&ShaderDiskReadHelper::OnOpComplete, this));
}

int ShaderDiskReadHelper::OpenNextEntryComplete(int rv) {
  DCHECK(CalledOnValidThread());
  // Called through OnOpComplete, so we know |cache_| is valid.
  if (rv == net::ERR_FAILED) {
    op_type_ = ITERATION_FINISHED;
    return net::OK;
  }

  if (rv < 0)
    return rv;

  op_type_ = READ_COMPLETE;
  buf_ = new net::IOBufferWithSize(entry_->GetDataSize(1));
  return entry_->ReadData(
      1,
      0,
      buf_.get(),
      buf_->size(),
      base::Bind(&ShaderDiskReadHelper::OnOpComplete, this));
}

int ShaderDiskReadHelper::ReadComplete(int rv) {
  DCHECK(CalledOnValidThread());
  // Called through OnOpComplete, so we know |cache_| is valid.
  if (rv && rv == buf_->size()) {
    GpuProcessHost* host = GpuProcessHost::FromID(host_id_);
    if (host)
      host->LoadedShader(entry_->GetKey(), std::string(buf_->data(),
                                                       buf_->size()));
  }

  buf_ = NULL;
  entry_->Close();
  entry_ = NULL;

  op_type_ = OPEN_NEXT;
  return net::OK;
}

int ShaderDiskReadHelper::IterationComplete(int rv) {
  DCHECK(CalledOnValidThread());
  // Called through OnOpComplete, so we know |cache_| is valid.
  cache_->backend()->EndEnumeration(&iter_);
  iter_ = NULL;
  op_type_ = TERMINATE;
  return net::OK;
}

ShaderDiskReadHelper::~ShaderDiskReadHelper() {
  if (entry_)
    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                            base::Bind(&EntryCloser, entry_));
}

ShaderClearHelper::ShaderClearHelper(scoped_refptr<ShaderDiskCache> cache,
                    const base::FilePath& path,
                    const base::Time& delete_begin,
                    const base::Time& delete_end,
                    const base::Closure& callback)
    : cache_(cache),
      op_type_(VERIFY_CACHE_SETUP),
      path_(path),
      delete_begin_(delete_begin),
      delete_end_(delete_end),
      callback_(callback) {
}

ShaderClearHelper::~ShaderClearHelper() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
}

void ShaderClearHelper::Clear() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DoClearShaderCache(net::OK);
}

void ShaderClearHelper::DoClearShaderCache(int rv) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Hold a ref to ourselves so when we do the CacheCleared call we don't get
  // auto-deleted when our ref count drops to zero.
  scoped_refptr<ShaderClearHelper> helper = this;

  while (rv != net::ERR_IO_PENDING) {
    switch (op_type_) {
      case VERIFY_CACHE_SETUP:
        rv = cache_->SetAvailableCallback(
            base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr()));
        op_type_ = DELETE_CACHE;
        break;
      case DELETE_CACHE:
        rv = cache_->Clear(
            delete_begin_, delete_end_,
            base::Bind(&ShaderClearHelper::DoClearShaderCache, AsWeakPtr()));
        op_type_ = TERMINATE;
        break;
      case TERMINATE:
        ShaderCacheFactory::GetInstance()->CacheCleared(path_);
        callback_.Run();
        rv = net::ERR_IO_PENDING;  // Break the loop.
        break;
      default:
        NOTREACHED();  // Invalid state provided.
        op_type_ = TERMINATE;
        break;
    }
  }
}

// static
ShaderCacheFactory* ShaderCacheFactory::GetInstance() {
  return Singleton<ShaderCacheFactory,
      LeakySingletonTraits<ShaderCacheFactory> >::get();
}

ShaderCacheFactory::ShaderCacheFactory() {
}

ShaderCacheFactory::~ShaderCacheFactory() {
}

void ShaderCacheFactory::SetCacheInfo(int32 client_id,
                                      const base::FilePath& path) {
  client_id_to_path_map_[client_id] = path;
}

void ShaderCacheFactory::RemoveCacheInfo(int32 client_id) {
  client_id_to_path_map_.erase(client_id);
}

scoped_refptr<ShaderDiskCache> ShaderCacheFactory::Get(int32 client_id) {
  ClientIdToPathMap::iterator iter =
      client_id_to_path_map_.find(client_id);
  if (iter == client_id_to_path_map_.end())
    return NULL;
  return ShaderCacheFactory::GetByPath(iter->second);
}

scoped_refptr<ShaderDiskCache> ShaderCacheFactory::GetByPath(
    const base::FilePath& path) {
  ShaderCacheMap::iterator iter = shader_cache_map_.find(path);
  if (iter != shader_cache_map_.end())
    return iter->second;

  ShaderDiskCache* cache = new ShaderDiskCache(path);
  cache->Init();
  return cache;
}

void ShaderCacheFactory::AddToCache(const base::FilePath& key,
                                    ShaderDiskCache* cache) {
  shader_cache_map_[key] = cache;
}

void ShaderCacheFactory::RemoveFromCache(const base::FilePath& key) {
  shader_cache_map_.erase(key);
}

void ShaderCacheFactory::ClearByPath(const base::FilePath& path,
                                     const base::Time& delete_begin,
                                     const base::Time& delete_end,
                                     const base::Closure& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(!callback.is_null());

  scoped_refptr<ShaderClearHelper> helper = new ShaderClearHelper(
      GetByPath(path), path, delete_begin, delete_end, callback);

  // We could receive requests to clear the same path with different
  // begin/end times. So, we keep a list of requests. If we haven't seen this
  // path before we kick off the clear and add it to the list. If we have see it
  // already, then we already have a clear running. We add this clear to the
  // list and wait for any previous clears to finish.
  ShaderClearMap::iterator iter = shader_clear_map_.find(path);
  if (iter != shader_clear_map_.end()) {
    iter->second.push(helper);
    return;
  }

  shader_clear_map_.insert(
      std::pair<base::FilePath, ShaderClearQueue>(path, ShaderClearQueue()));
  shader_clear_map_[path].push(helper);
  helper->Clear();
}

void ShaderCacheFactory::CacheCleared(const base::FilePath& path) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  ShaderClearMap::iterator iter = shader_clear_map_.find(path);
  if (iter == shader_clear_map_.end()) {
    LOG(ERROR) << "Completed clear but missing clear helper.";
    return;
  }

  iter->second.pop();

  // If there are remaining items in the list we trigger the Clear on the
  // next one.
  if (!iter->second.empty()) {
    iter->second.front()->Clear();
    return;
  }

  shader_clear_map_.erase(path);
}

ShaderDiskCache::ShaderDiskCache(const base::FilePath& cache_path)
    : cache_available_(false),
      host_id_(0),
      cache_path_(cache_path),
      is_initialized_(false) {
  ShaderCacheFactory::GetInstance()->AddToCache(cache_path_, this);
}

ShaderDiskCache::~ShaderDiskCache() {
  ShaderCacheFactory::GetInstance()->RemoveFromCache(cache_path_);
}

void ShaderDiskCache::Init() {
  if (is_initialized_) {
    NOTREACHED();  // can't initialize disk cache twice.
    return;
  }
  is_initialized_ = true;

  int rv = disk_cache::CreateCacheBackend(
      net::SHADER_CACHE,
      net::CACHE_BACKEND_DEFAULT,
      cache_path_.Append(kGpuCachePath),
      gpu::kDefaultMaxProgramCacheMemoryBytes,
      true,
      BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(),
      NULL,
      &backend_,
      base::Bind(&ShaderDiskCache::CacheCreatedCallback, this));

  if (rv == net::OK)
    cache_available_ = true;
}

void ShaderDiskCache::Cache(const std::string& key, const std::string& shader) {
  if (!cache_available_)
    return;

  scoped_refptr<ShaderDiskCacheEntry> shim =
      new ShaderDiskCacheEntry(AsWeakPtr(), key, shader);
  shim->Cache();

  entry_map_[shim] = shim;
}

int ShaderDiskCache::Clear(
    const base::Time begin_time, const base::Time end_time,
    const net::CompletionCallback& completion_callback) {
  int rv;
  if (begin_time.is_null()) {
    rv = backend_->DoomAllEntries(completion_callback);
  } else {
    rv = backend_->DoomEntriesBetween(begin_time, end_time,
                                      completion_callback);
  }
  return rv;
}

int32 ShaderDiskCache::Size() {
  if (!cache_available_)
    return -1;
  return backend_->GetEntryCount();
}

int ShaderDiskCache::SetAvailableCallback(
    const net::CompletionCallback& callback) {
  if (cache_available_)
    return net::OK;
  available_callback_ = callback;
  return net::ERR_IO_PENDING;
}

void ShaderDiskCache::CacheCreatedCallback(int rv) {
  if (rv != net::OK) {
    LOG(ERROR) << "Shader Cache Creation failed: " << rv;
    return;
  }
  helper_ = new ShaderDiskReadHelper(AsWeakPtr(), host_id_);
  helper_->LoadCache();
}

void ShaderDiskCache::EntryComplete(void* entry) {
  entry_map_.erase(entry);

  if (entry_map_.empty() && !cache_complete_callback_.is_null())
    cache_complete_callback_.Run(net::OK);
}

void ShaderDiskCache::ReadComplete() {
  helper_ = NULL;

  // The cache is considered available after we have finished reading any
  // of the old cache values off disk. This prevents a potential race where we
  // are reading from disk and execute a cache clear at the same time.
  cache_available_ = true;
  if (!available_callback_.is_null()) {
    available_callback_.Run(net::OK);
    available_callback_.Reset();
  }
}

int ShaderDiskCache::SetCacheCompleteCallback(
    const net::CompletionCallback& callback) {
  if (entry_map_.empty()) {
    return net::OK;
  }
  cache_complete_callback_ = callback;
  return net::ERR_IO_PENDING;
}

}  // namespace content