| // Copyright 2017 The Chromium Authors | |
| // 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 <algorithm> | |
| #include <cmath> | |
| #include <string_view> | |
| #include <utility> | |
| #include "base/base64.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/feature_list.h" | |
| #include "base/memory_coordinator/memory_coordinator_features.h" | |
| #include "base/memory_coordinator/traits.h" | |
| #include "base/memory_coordinator/utils.h" | |
| #include "base/metrics/histogram_functions.h" | |
| #include "base/metrics/histogram_macros.h" | |
| #include "base/strings/string_view_util.h" | |
| #include "ui/gl/gl_bindings.h" | |
| #include "ui/gl/gl_display.h" | |
| #include "ui/gl/gl_surface_egl.h" | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| bool BlobCacheExtensionAvailable(gl::GLDisplayEGL* gl_display) { | |
| // The display should be initialized if the extension is available. | |
| return gl_display->ext->b_EGL_ANDROID_blob_cache; | |
| } | |
| // 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; | |
| // Blob cache function pointers can only be set once per EGLDisplay. Using a | |
| // single bool is not technically correct, but it's acceptable since we only | |
| // have one EGLDisplay in practice. | |
| bool g_blob_cache_funcs_set = false; | |
| constexpr base::MemoryConsumerTraits kPassthroughProgramCacheTraits( | |
| // Default capacity is small, under 10MB. | |
| base::MemoryConsumerTraits::EstimatedMemoryUsage::kSmall, | |
| // Evicting entries requires traversing the LRU cache. | |
| base::MemoryConsumerTraits::ReleaseMemoryCost::kRequiresTraversal, | |
| // Shaders can be re-compiled and re-linked if evicted. | |
| base::MemoryConsumerTraits::InformationRetention::kLossless, | |
| // Asynchronous since AsyncMemoryConsumerRegistration is used. | |
| base::MemoryConsumerTraits::ExecutionType::kAsynchronous, | |
| // Compiling and linking shaders is highly CPU intensive. | |
| base::MemoryConsumerTraits::RecreateMemoryCost::kExpensive); | |
| } // 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_(ProgramLRUCache::NO_AUTO_EVICT), | |
| memory_consumer_registration_( | |
| "PassthroughProgramCache", | |
| kPassthroughProgramCacheTraits, | |
| this, | |
| base::AsyncMemoryConsumerRegistration::CheckUnregister::kDisabled), | |
| current_max_size_bytes_(max_cache_size_bytes) { | |
| gl::GLDisplayEGL* gl_display = gl::GLSurfaceEGL::GetGLDisplayEGL(); | |
| EGLDisplay egl_display = gl_display->GetDisplay(); | |
| DCHECK(!g_program_cache); | |
| g_program_cache = this; | |
| // display is EGL_NO_DISPLAY during unittests. | |
| if (egl_display != EGL_NO_DISPLAY && !g_blob_cache_funcs_set && | |
| BlobCacheExtensionAvailable(gl_display)) { | |
| // Register the blob cache callbacks. | |
| eglSetBlobCacheFuncsANDROID(egl_display, BlobCacheSet, BlobCacheGet); | |
| g_blob_cache_funcs_set = true; | |
| } | |
| } | |
| PassthroughProgramCache::~PassthroughProgramCache() { | |
| // 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; | |
| } | |
| void PassthroughProgramCache::ClearBackend() { | |
| base::AutoLock auto_lock(lock_); | |
| 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(); | |
| } | |
| 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) { | |
| base::AutoLock auto_lock(lock_); | |
| 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)); | |
| } | |
| size_t PassthroughProgramCache::Trim(size_t limit) { | |
| base::AutoLock auto_lock(lock_); | |
| return TrimLocked(limit); | |
| } | |
| void PassthroughProgramCache::OnUpdateMemoryLimit() { | |
| base::AutoLock auto_lock(lock_); | |
| if (!base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) { | |
| return; | |
| } | |
| // To match previous behavior, the size must be 1/4 at 50% memory limit. | |
| double ratio = std::clamp(memory_limit_ratio(), 0.0, 1.0); | |
| size_t target_size = max_size_bytes() * std::pow(ratio, 2.0); | |
| current_max_size_bytes_ = std::max(curr_size_bytes_, target_size); | |
| } | |
| void PassthroughProgramCache::OnReleaseMemory() { | |
| base::AutoLock auto_lock(lock_); | |
| if (base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) { | |
| // To match previous behavior, the size must be 1/4 at 50% memory limit. | |
| double ratio = std::clamp(memory_limit_ratio(), 0.0, 1.0); | |
| size_t target_size = max_size_bytes() * std::pow(ratio, 2.0); | |
| current_max_size_bytes_ = target_size; | |
| TrimLocked(current_max_size_bytes_); | |
| return; | |
| } | |
| int limit = memory_limit(); | |
| if (limit <= base::kCriticalMemoryPressureThreshold) { | |
| TrimLocked(0); | |
| } else if (limit <= base::kModerateMemoryPressureThreshold) { | |
| TrimLocked(max_size_bytes() / 4); | |
| } | |
| } | |
| bool PassthroughProgramCache::CacheEnabled() const { | |
| return !disable_gpu_shader_disk_cache_; | |
| } | |
| void PassthroughProgramCache::Set(Key&& key, | |
| Value&& value, | |
| CacheProgramCallback callback) { | |
| base::AutoLock auto_lock(lock_); | |
| // If the value is so big it will never fit in the cache, throw it away. | |
| if (value.size() > GetCurrentMaxSizeBytes()) { | |
| return; | |
| } | |
| // Evict any cached program with the same key in favor of the least recently | |
| // accessed. | |
| ProgramLRUCache::iterator existing = store_.Peek(key); | |
| if (existing != store_.end()) { | |
| store_.Erase(existing); | |
| } | |
| // If the cache is overflowing, remove some old entries. | |
| DCHECK(GetCurrentMaxSizeBytes() >= value.size()); | |
| TrimLocked(GetCurrentMaxSizeBytes() - value.size()); | |
| // If callback is set, notify that there was a new/updated blob entry so it | |
| // can be stored in disk. Note that this is done before the Put() call as | |
| // that consumes `value`. | |
| CacheProgramCallback callback_with_fallback = | |
| callback ? callback : cache_program_callback_; | |
| if (callback_with_fallback) { | |
| // Convert the key and binary to base-64 string form. | |
| std::string key_string_64 = base::Base64Encode(key); | |
| std::string value_string_64 = base::Base64Encode(value); | |
| callback_with_fallback.Run(key_string_64, value_string_64); | |
| } | |
| store_.Put(key, ProgramCacheValue(std::move(value), this)); | |
| } | |
| size_t PassthroughProgramCache::Get(const Key& key, | |
| void* out_value, | |
| size_t value_size) { | |
| // Note that the |lock_| should be held during whole time ProgramCacheValue is | |
| // being accessed below. | |
| base::AutoLock auto_lock(lock_); | |
| ProgramLRUCache::iterator found = store_.Get(key); | |
| UMA_HISTOGRAM_BOOLEAN("Gpu.PassthroughProgramCacheLoadHitInCache", | |
| found != store_.end()); | |
| if (found == store_.end()) { | |
| return 0; | |
| } | |
| const PassthroughProgramCache::Value& entry_value = found->second.data(); | |
| if (value_size > 0) { | |
| if (static_cast<size_t>(value_size) >= entry_value.size()) { | |
| UNSAFE_TODO(memcpy(out_value, entry_value.data(), entry_value.size())); | |
| } | |
| } | |
| return entry_value.size(); | |
| } | |
| EGLsizeiANDROID PassthroughProgramCache::BlobCacheGetImpl( | |
| const void* key, | |
| EGLsizeiANDROID key_size, | |
| void* value, | |
| EGLsizeiANDROID value_size) { | |
| if (key_size < 0) { | |
| return 0; | |
| } | |
| const uint8_t* key_begin = reinterpret_cast<const uint8_t*>(key); | |
| PassthroughProgramCache::Key entry_key(key_begin, | |
| UNSAFE_TODO(key_begin + key_size)); | |
| return Get(entry_key, value, value_size); | |
| } | |
| void PassthroughProgramCache::BlobCacheSetImpl(const void* key, | |
| EGLsizeiANDROID key_size, | |
| const void* value, | |
| EGLsizeiANDROID value_size) { | |
| 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, | |
| UNSAFE_TODO(key_begin + key_size)); | |
| const uint8_t* value_begin = reinterpret_cast<const uint8_t*>(value); | |
| PassthroughProgramCache::Value entry_value( | |
| value_begin, UNSAFE_TODO(value_begin + value_size)); | |
| // Pass a null callback to use the default cache_program_callback_ | |
| Set(std::move(entry_key), std::move(entry_value), CacheProgramCallback()); | |
| } | |
| size_t PassthroughProgramCache::TrimLocked(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_; | |
| } | |
| size_t PassthroughProgramCache::GetCurrentMaxSizeBytes() const { | |
| if (base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) { | |
| return current_max_size_bytes_; | |
| } | |
| return max_size_bytes(); | |
| } | |
| void PassthroughProgramCache::BlobCacheSet(const void* key, | |
| EGLsizeiANDROID key_size, | |
| const void* value, | |
| EGLsizeiANDROID value_size) { | |
| if (!g_program_cache) | |
| return; | |
| g_program_cache->BlobCacheSetImpl(key, key_size, value, value_size); | |
| } | |
| EGLsizeiANDROID PassthroughProgramCache::BlobCacheGet( | |
| const void* key, | |
| EGLsizeiANDROID key_size, | |
| void* value, | |
| EGLsizeiANDROID value_size) { | |
| if (!g_program_cache) | |
| return 0; | |
| return g_program_cache->BlobCacheGetImpl(key, key_size, value, 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() { | |
| if (program_cache_) { | |
| program_cache_->curr_size_bytes_ -= program_blob_.size(); | |
| } | |
| } | |
| PassthroughProgramCache::ProgramCacheValue::ProgramCacheValue( | |
| ProgramCacheValue&& other) | |
| : program_blob_(std::move(other.program_blob_)), | |
| program_cache_(std::exchange(other.program_cache_, nullptr)) {} | |
| PassthroughProgramCache::ProgramCacheValue& | |
| PassthroughProgramCache::ProgramCacheValue::operator=( | |
| ProgramCacheValue&& other) { | |
| program_blob_ = std::move(other.program_blob_); | |
| program_cache_ = std::exchange(other.program_cache_, nullptr); | |
| return *this; | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |