blob: 0ec587a2cf3e219c1daf4db414497850ab8e65cb [file] [log] [blame]
// Copyright (c) 2017 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 "gpu/command_buffer/service/passthrough_program_cache.h"
#include <stddef.h>
#include "base/base64.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_piece.h"
#include "ui/gl/gl_bindings.h"
#if defined(USE_EGL)
#include "ui/gl/gl_surface_egl.h"
#endif // defined(USE_EGL)
namespace gpu {
namespace gles2 {
namespace {
bool BlobCacheExtensionAvailable() {
#if defined(USE_EGL)
// The display should be initialized if the extension is available.
return gl::g_driver_egl.ext.b_EGL_ANDROID_blob_cache;
#else
return false;
#endif // defined(USE_EGL)
}
// EGL_ANDROID_blob_cache doesn't give user pointer to the callbacks so we are
// forced to have this be global.
PassthroughProgramCache* g_program_cache = nullptr;
} // namespace
PassthroughProgramCache::PassthroughProgramCache(
size_t max_cache_size_bytes,
bool disable_gpu_shader_disk_cache)
: ProgramCache(max_cache_size_bytes),
disable_gpu_shader_disk_cache_(disable_gpu_shader_disk_cache),
curr_size_bytes_(0),
store_(ProgramMRUCache::NO_AUTO_EVICT) {
#if defined(USE_EGL)
EGLDisplay display = gl::GLSurfaceEGL::GetHardwareDisplay();
DCHECK(!g_program_cache);
g_program_cache = this;
// display is EGL_NO_DISPLAY during unittests.
if (display != EGL_NO_DISPLAY && BlobCacheExtensionAvailable()) {
// Register the blob cache callbacks.
eglSetBlobCacheFuncsANDROID(display, BlobCacheSet, BlobCacheGet);
}
#endif // defined(USE_EGL)
}
PassthroughProgramCache::~PassthroughProgramCache() {
#if defined(USE_EGL)
// Clear up the blob cache callbacks. Note that this not allowed by the
// EGL_ANDROID_blob_cache spec, so we just set the pointer to this object to
// nullptr as a workaround. The callbacks don't work with this pointer
// missing.
g_program_cache = nullptr;
#endif // defined(USE_EGL)
}
void PassthroughProgramCache::ClearBackend() {
store_.Clear();
DCHECK_EQ(0U, curr_size_bytes_);
}
ProgramCache::ProgramLoadResult PassthroughProgramCache::LoadLinkedProgram(
GLuint program,
Shader* shader_a,
Shader* shader_b,
const LocationMap* bind_attrib_location_map,
const std::vector<std::string>& transform_feedback_varyings,
GLenum transform_feedback_buffer_mode,
DecoderClient* client) {
NOTREACHED();
return PROGRAM_LOAD_FAILURE;
}
void PassthroughProgramCache::SaveLinkedProgram(
GLuint program,
const Shader* shader_a,
const Shader* shader_b,
const LocationMap* bind_attrib_location_map,
const std::vector<std::string>& transform_feedback_varyings,
GLenum transform_feedback_buffer_mode,
DecoderClient* client) {
NOTREACHED();
}
void PassthroughProgramCache::LoadProgram(const std::string& key,
const std::string& program) {
if (!CacheEnabled()) {
return;
}
std::string key_decoded;
std::string program_decoded;
base::Base64Decode(key, &key_decoded);
base::Base64Decode(program, &program_decoded);
Key entry_key(key_decoded.begin(), key_decoded.end());
Value entry_value(program_decoded.begin(), program_decoded.end());
store_.Put(entry_key, ProgramCacheValue(std::move(entry_value), this));
UMA_HISTOGRAM_COUNTS_1M("GPU.ProgramCache.MemorySizeAfterKb",
curr_size_bytes_ / 1024);
}
size_t PassthroughProgramCache::Trim(size_t limit) {
size_t initial_size = curr_size_bytes_;
while (curr_size_bytes_ > limit) {
DCHECK(!store_.empty());
store_.Erase(store_.rbegin());
}
return initial_size - curr_size_bytes_;
}
bool PassthroughProgramCache::CacheEnabled() const {
return !disable_gpu_shader_disk_cache_;
}
void PassthroughProgramCache::Set(Key&& key, Value&& value) {
// If the value is so big it will never fit in the cache, throw it away.
if (value.size() > max_size_bytes())
return;
UMA_HISTOGRAM_COUNTS_1M("GPU.ProgramCache.MemorySizeBeforeKb",
curr_size_bytes_ / 1024);
// Evict any cached program with the same key in favor of the least recently
// accessed.
ProgramMRUCache::iterator existing = store_.Peek(key);
if (existing != store_.end())
store_.Erase(existing);
// If the cache is overflowing, remove some old entries.
DCHECK(max_size_bytes() >= value.size());
Trim(max_size_bytes() - value.size());
// If callback is set, notify that there was a new/updated blob entry so it
// can be soted in disk. Note that this is done before the Put() call as that
// consumes `value`.
if (cache_program_callback_) {
// Convert the key and binary to string form.
base::StringPiece key_string(reinterpret_cast<const char*>(key.data()),
key.size());
base::StringPiece value_string(reinterpret_cast<const char*>(value.data()),
value.size());
std::string key_string_64;
std::string value_string_64;
base::Base64Encode(key_string, &key_string_64);
base::Base64Encode(value_string, &value_string_64);
cache_program_callback_.Run(key_string_64, value_string_64);
}
store_.Put(key, ProgramCacheValue(std::move(value), this));
UMA_HISTOGRAM_COUNTS_1M("GPU.ProgramCache.MemorySizeAfterKb",
curr_size_bytes_ / 1024);
}
const PassthroughProgramCache::ProgramCacheValue* PassthroughProgramCache::Get(
const Key& key) {
ProgramMRUCache::iterator found = store_.Get(key);
return found == store_.end() ? nullptr : &found->second;
}
void PassthroughProgramCache::BlobCacheSet(const void* key,
EGLsizeiANDROID key_size,
const void* value,
EGLsizeiANDROID value_size) {
if (!g_program_cache)
return;
if (key_size < 0 || value_size < 0)
return;
const uint8_t* key_begin = reinterpret_cast<const uint8_t*>(key);
PassthroughProgramCache::Key entry_key(key_begin, key_begin + key_size);
const uint8_t* value_begin = reinterpret_cast<const uint8_t*>(value);
PassthroughProgramCache::Value entry_value(value_begin,
value_begin + value_size);
g_program_cache->Set(std::move(entry_key), std::move(entry_value));
}
EGLsizeiANDROID PassthroughProgramCache::BlobCacheGet(
const void* key,
EGLsizeiANDROID key_size,
void* value,
EGLsizeiANDROID value_size) {
if (!g_program_cache)
return 0;
if (key_size < 0)
return 0;
const uint8_t* key_begin = reinterpret_cast<const uint8_t*>(key);
PassthroughProgramCache::Key entry_key(key_begin, key_begin + key_size);
const PassthroughProgramCache::ProgramCacheValue* cacheValue =
g_program_cache->Get(std::move(entry_key));
if (!cacheValue)
return 0;
const PassthroughProgramCache::Value& entry_value = cacheValue->data();
if (value_size > 0) {
if (static_cast<size_t>(value_size) >= entry_value.size())
memcpy(value, entry_value.data(), entry_value.size());
}
return entry_value.size();
}
PassthroughProgramCache::ProgramCacheValue::ProgramCacheValue(
PassthroughProgramCache::Value&& program_blob,
PassthroughProgramCache* program_cache)
: program_blob_(std::move(program_blob)), program_cache_(program_cache) {
program_cache_->curr_size_bytes_ += program_blob_.size();
}
PassthroughProgramCache::ProgramCacheValue::~ProgramCacheValue() {
program_cache_->curr_size_bytes_ -= program_blob_.size();
}
PassthroughProgramCache::ProgramCacheValue::ProgramCacheValue(
ProgramCacheValue&& other) = default;
PassthroughProgramCache::ProgramCacheValue&
PassthroughProgramCache::ProgramCacheValue::operator=(
ProgramCacheValue&& other) = default;
} // namespace gles2
} // namespace gpu