| // Copyright 2012 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/memory_program_cache.h" | |
| #include <stddef.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <cmath> | |
| #include "base/base64.h" | |
| #include "base/check.h" | |
| #include "base/command_line.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/feature_list.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback.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/numerics/checked_math.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/system/sys_info.h" | |
| #include "build/build_config.h" | |
| #include "gpu/command_buffer/common/constants.h" | |
| #include "gpu/command_buffer/common/shm_count.h" | |
| #include "gpu/command_buffer/service/disk_cache_proto.pb.h" | |
| #include "gpu/command_buffer/service/gl_utils.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_decoder.h" | |
| #include "gpu/command_buffer/service/shader_manager.h" | |
| #include "gpu/config/gpu_preferences.h" | |
| #include "third_party/zlib/zlib.h" | |
| #include "ui/gl/gl_bindings.h" | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| template <typename T, size_t N> | |
| std::array<std::remove_const_t<T>, N> SpanToArray(base::span<T, N> s) { | |
| std::array<std::remove_const_t<T>, N> array; | |
| base::span(array).copy_from(s); | |
| return array; | |
| } | |
| void FillShaderVariableProto( | |
| ShaderVariableProto* proto, const sh::ShaderVariable& variable) { | |
| proto->set_type(variable.type); | |
| proto->set_precision(variable.precision); | |
| proto->set_name(variable.name); | |
| proto->set_mapped_name(variable.mappedName); | |
| proto->set_array_size(variable.getOutermostArraySize()); | |
| proto->set_static_use(variable.staticUse); | |
| for (size_t ii = 0; ii < variable.fields.size(); ++ii) { | |
| ShaderVariableProto* field = proto->add_fields(); | |
| FillShaderVariableProto(field, variable.fields[ii]); | |
| } | |
| proto->set_struct_name(variable.getStructName()); | |
| } | |
| void FillShaderAttributeProto( | |
| ShaderAttributeProto* proto, const sh::Attribute& attrib) { | |
| FillShaderVariableProto(proto->mutable_basic(), attrib); | |
| proto->set_location(attrib.location); | |
| } | |
| void FillShaderUniformProto( | |
| ShaderUniformProto* proto, const sh::Uniform& uniform) { | |
| FillShaderVariableProto(proto->mutable_basic(), uniform); | |
| } | |
| void FillShaderVaryingProto( | |
| ShaderVaryingProto* proto, const sh::Varying& varying) { | |
| FillShaderVariableProto(proto->mutable_basic(), varying); | |
| proto->set_interpolation(varying.interpolation); | |
| proto->set_is_invariant(varying.isInvariant); | |
| } | |
| void FillShaderOutputVariableProto(ShaderOutputVariableProto* proto, | |
| const sh::OutputVariable& attrib) { | |
| FillShaderVariableProto(proto->mutable_basic(), attrib); | |
| proto->set_location(attrib.location); | |
| } | |
| void FillShaderInterfaceBlockFieldProto( | |
| ShaderInterfaceBlockFieldProto* proto, | |
| const sh::InterfaceBlockField& interfaceBlockField) { | |
| FillShaderVariableProto(proto->mutable_basic(), interfaceBlockField); | |
| proto->set_is_row_major_layout(interfaceBlockField.isRowMajorLayout); | |
| } | |
| void FillShaderInterfaceBlockProto(ShaderInterfaceBlockProto* proto, | |
| const sh::InterfaceBlock& interfaceBlock) { | |
| proto->set_name(interfaceBlock.name); | |
| proto->set_mapped_name(interfaceBlock.mappedName); | |
| proto->set_instance_name(interfaceBlock.instanceName); | |
| proto->set_array_size(interfaceBlock.arraySize); | |
| proto->set_layout(interfaceBlock.layout); | |
| proto->set_static_use(interfaceBlock.staticUse); | |
| for (size_t ii = 0; ii < interfaceBlock.fields.size(); ++ii) { | |
| ShaderInterfaceBlockFieldProto* field = proto->add_fields(); | |
| FillShaderInterfaceBlockFieldProto(field, interfaceBlock.fields[ii]); | |
| } | |
| } | |
| void FillShaderProto(ShaderProto* proto, | |
| ProgramCache::HashView sha, | |
| const Shader* shader) { | |
| proto->set_sha(sha.data(), sha.size()); | |
| for (const auto& [string, attr] : shader->attrib_map()) { | |
| ShaderAttributeProto* info = proto->add_attribs(); | |
| FillShaderAttributeProto(info, attr); | |
| } | |
| for (const auto& [string, uniform] : shader->uniform_map()) { | |
| ShaderUniformProto* info = proto->add_uniforms(); | |
| FillShaderUniformProto(info, uniform); | |
| } | |
| for (const auto& [string, varying] : shader->varying_map()) { | |
| ShaderVaryingProto* info = proto->add_varyings(); | |
| FillShaderVaryingProto(info, varying); | |
| } | |
| for (const sh::OutputVariable& var : shader->output_variable_list()) { | |
| ShaderOutputVariableProto* info = proto->add_output_variables(); | |
| FillShaderOutputVariableProto(info, var); | |
| } | |
| for (const auto& [string, interface_block] : shader->interface_block_map()) { | |
| ShaderInterfaceBlockProto* info = proto->add_interface_blocks(); | |
| FillShaderInterfaceBlockProto(info, interface_block); | |
| } | |
| } | |
| void RetrieveShaderVariableInfo( | |
| const ShaderVariableProto& proto, sh::ShaderVariable* variable) { | |
| variable->type = proto.type(); | |
| variable->precision = proto.precision(); | |
| variable->name = proto.name(); | |
| variable->mappedName = proto.mapped_name(); | |
| variable->setArraySize(proto.array_size()); | |
| variable->staticUse = proto.static_use(); | |
| variable->fields.resize(proto.fields_size()); | |
| for (int ii = 0; ii < proto.fields_size(); ++ii) | |
| RetrieveShaderVariableInfo(proto.fields(ii), &(variable->fields[ii])); | |
| variable->setStructName(proto.struct_name()); | |
| } | |
| void RetrieveShaderAttributeInfo( | |
| const ShaderAttributeProto& proto, AttributeMap* map) { | |
| sh::Attribute attrib; | |
| RetrieveShaderVariableInfo(proto.basic(), &attrib); | |
| attrib.location = proto.location(); | |
| (*map)[proto.basic().mapped_name()] = attrib; | |
| } | |
| void RetrieveShaderUniformInfo( | |
| const ShaderUniformProto& proto, UniformMap* map) { | |
| sh::Uniform uniform; | |
| RetrieveShaderVariableInfo(proto.basic(), &uniform); | |
| (*map)[proto.basic().mapped_name()] = uniform; | |
| } | |
| void RetrieveShaderVaryingInfo( | |
| const ShaderVaryingProto& proto, VaryingMap* map) { | |
| sh::Varying varying; | |
| RetrieveShaderVariableInfo(proto.basic(), &varying); | |
| varying.interpolation = static_cast<sh::InterpolationType>( | |
| proto.interpolation()); | |
| varying.isInvariant = proto.is_invariant(); | |
| (*map)[proto.basic().mapped_name()] = varying; | |
| } | |
| void RetrieveShaderOutputVariableInfo(const ShaderOutputVariableProto& proto, | |
| OutputVariableList* list) { | |
| sh::OutputVariable output_variable; | |
| RetrieveShaderVariableInfo(proto.basic(), &output_variable); | |
| output_variable.location = proto.location(); | |
| list->push_back(output_variable); | |
| } | |
| void RetrieveShaderInterfaceBlockFieldInfo( | |
| const ShaderInterfaceBlockFieldProto& proto, | |
| sh::InterfaceBlockField* interface_block_field) { | |
| RetrieveShaderVariableInfo(proto.basic(), interface_block_field); | |
| interface_block_field->isRowMajorLayout = proto.is_row_major_layout(); | |
| } | |
| void RetrieveShaderInterfaceBlockInfo(const ShaderInterfaceBlockProto& proto, | |
| InterfaceBlockMap* map) { | |
| sh::InterfaceBlock interface_block; | |
| interface_block.name = proto.name(); | |
| interface_block.mappedName = proto.mapped_name(); | |
| interface_block.instanceName = proto.instance_name(); | |
| interface_block.arraySize = proto.array_size(); | |
| interface_block.layout = static_cast<sh::BlockLayoutType>(proto.layout()); | |
| interface_block.staticUse = proto.static_use(); | |
| interface_block.fields.resize(proto.fields_size()); | |
| for (int ii = 0; ii < proto.fields_size(); ++ii) { | |
| RetrieveShaderInterfaceBlockFieldInfo(proto.fields(ii), | |
| &(interface_block.fields[ii])); | |
| } | |
| (*map)[proto.mapped_name()] = interface_block; | |
| } | |
| void RunShaderCallback(DecoderClient* client, | |
| GpuProgramProto* proto, | |
| ProgramCache::HashView program_sha) { | |
| std::string shader; | |
| proto->SerializeToString(&shader); | |
| std::string key = base::Base64Encode(program_sha); | |
| client->CacheBlob(gpu::GpuDiskCacheType::kGlShaders, key, shader); | |
| } | |
| bool ProgramBinaryExtensionsAvailable() { | |
| return gl::g_current_gl_driver && | |
| gl::g_current_gl_driver->ext.b_GL_OES_get_program_binary; | |
| } | |
| std::vector<uint8_t> CompressData(const std::vector<uint8_t>& data) { | |
| uLongf compressed_size = compressBound(data.size()); | |
| std::vector<uint8_t> compressed_data(compressed_size); | |
| // Level indicates a trade-off between compression and speed. Level 1 | |
| // indicates fastest speed (with worst compression). | |
| auto result = compress2(compressed_data.data(), &compressed_size, data.data(), | |
| data.size(), 1 /* level */); | |
| // It should be impossible for compression to fail with the provided | |
| // parameters. | |
| CHECK_EQ(result, Z_OK); | |
| compressed_data.resize(compressed_size); | |
| compressed_data.shrink_to_fit(); | |
| return compressed_data; | |
| } | |
| // Returns an empty vector if decompression fails. | |
| std::vector<uint8_t> DecompressData(const std::vector<uint8_t>& data, | |
| size_t decompressed_size, | |
| size_t max_size_bytes) { | |
| std::vector<uint8_t> decompressed_data(decompressed_size); | |
| uLongf decompressed_size_out = | |
| static_cast<uLongf>(decompressed_size); | |
| auto result = uncompress(decompressed_data.data(), &decompressed_size_out, | |
| data.data(), data.size()); | |
| bool success = | |
| result == Z_OK && decompressed_data.size() == decompressed_size_out; | |
| if (!success) | |
| return std::vector<uint8_t>(); | |
| return decompressed_data; | |
| } | |
| bool CompressProgramBinaries() { | |
| #if !BUILDFLAG(IS_ANDROID) | |
| return false; | |
| #else // !BUILDFLAG(IS_ANDROID) | |
| return base::SysInfo::IsLowEndDevice(); | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| } | |
| constexpr base::MemoryConsumerTraits kMemoryProgramCacheTraits( | |
| // 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 | |
| MemoryProgramCache::MemoryProgramCache( | |
| size_t max_cache_size_bytes, | |
| bool disable_gpu_shader_disk_cache, | |
| bool disable_program_caching_for_transform_feedback, | |
| GpuProcessShmCount* use_shader_cache_shm_count) | |
| : ProgramCache(max_cache_size_bytes), | |
| disable_gpu_shader_disk_cache_(disable_gpu_shader_disk_cache), | |
| disable_program_caching_for_transform_feedback_( | |
| disable_program_caching_for_transform_feedback), | |
| compress_program_binaries_(CompressProgramBinaries()), | |
| curr_size_bytes_(0), | |
| store_(ProgramLRUCache::NO_AUTO_EVICT), | |
| use_shader_cache_shm_count_(use_shader_cache_shm_count), | |
| memory_consumer_registration_( | |
| "MemoryProgramCache", | |
| kMemoryProgramCacheTraits, | |
| this, | |
| base::AsyncMemoryConsumerRegistration::CheckUnregister::kDisabled), | |
| current_max_size_bytes_(max_cache_size_bytes) {} | |
| MemoryProgramCache::~MemoryProgramCache() = default; | |
| void MemoryProgramCache::ClearBackend() { | |
| store_.Clear(); | |
| DCHECK_EQ(0U, curr_size_bytes_); | |
| } | |
| size_t MemoryProgramCache::GetCurrentMaxSizeBytes() const { | |
| if (base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) { | |
| return current_max_size_bytes_; | |
| } | |
| return max_size_bytes(); | |
| } | |
| ProgramCache::ProgramLoadResult MemoryProgramCache::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) { | |
| if (!ProgramBinaryExtensionsAvailable()) { | |
| // Early exit if this context can't support program binaries | |
| return PROGRAM_LOAD_FAILURE; | |
| } | |
| Hash a_sha; | |
| Hash b_sha; | |
| DCHECK(shader_a && !shader_a->last_compiled_source().empty() && | |
| shader_b && !shader_b->last_compiled_source().empty()); | |
| ComputeShaderHash( | |
| shader_a->last_compiled_signature(), a_sha); | |
| ComputeShaderHash( | |
| shader_b->last_compiled_signature(), b_sha); | |
| Hash program_sha; | |
| ComputeProgramHash(a_sha, b_sha, bind_attrib_location_map, | |
| transform_feedback_varyings, | |
| transform_feedback_buffer_mode, program_sha); | |
| ProgramLRUCache::iterator found = store_.Get(program_sha); | |
| if (found == store_.end()) { | |
| return PROGRAM_LOAD_FAILURE; | |
| } | |
| const scoped_refptr<ProgramCacheValue> value = found->second; | |
| const std::vector<uint8_t>& decoded = | |
| value->is_compressed() | |
| ? DecompressData(value->data(), value->decompressed_length(), | |
| GetCurrentMaxSizeBytes()) | |
| : value->data(); | |
| if (decoded.empty()) { | |
| // Decompression failure. | |
| DCHECK(value->is_compressed()); | |
| return PROGRAM_LOAD_FAILURE; | |
| } | |
| { | |
| GpuProcessShmCount::ScopedIncrement scoped_increment( | |
| use_shader_cache_shm_count_); | |
| glProgramBinary(program, value->format(), | |
| static_cast<const GLvoid*>(decoded.data()), decoded.size()); | |
| } | |
| GLint success = 0; | |
| glGetProgramiv(program, GL_LINK_STATUS, &success); | |
| if (success == GL_FALSE) { | |
| return PROGRAM_LOAD_FAILURE; | |
| } | |
| shader_a->set_attrib_map(value->attrib_map_0()); | |
| shader_a->set_uniform_map(value->uniform_map_0()); | |
| shader_a->set_varying_map(value->varying_map_0()); | |
| shader_a->set_output_variable_list(value->output_variable_list_0()); | |
| shader_a->set_interface_block_map(value->interface_block_map_0()); | |
| shader_b->set_attrib_map(value->attrib_map_1()); | |
| shader_b->set_uniform_map(value->uniform_map_1()); | |
| shader_b->set_varying_map(value->varying_map_1()); | |
| shader_b->set_output_variable_list(value->output_variable_list_1()); | |
| shader_b->set_interface_block_map(value->interface_block_map_1()); | |
| if (!disable_gpu_shader_disk_cache_) { | |
| std::unique_ptr<GpuProgramProto> proto( | |
| GpuProgramProto::default_instance().New()); | |
| proto->set_sha(program_sha.data(), program_sha.size()); | |
| proto->set_format(value->format()); | |
| proto->set_program(value->data().data(), value->data().size()); | |
| proto->set_program_is_compressed(value->is_compressed()); | |
| proto->set_program_decompressed_length(value->decompressed_length()); | |
| FillShaderProto(proto->mutable_vertex_shader(), a_sha, shader_a); | |
| FillShaderProto(proto->mutable_fragment_shader(), b_sha, shader_b); | |
| RunShaderCallback(client, proto.get(), program_sha); | |
| } | |
| return PROGRAM_LOAD_SUCCESS; | |
| } | |
| void MemoryProgramCache::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) { | |
| if (!ProgramBinaryExtensionsAvailable()) { | |
| // Early exit if this context can't support program binaries | |
| return; | |
| } | |
| if (disable_program_caching_for_transform_feedback_ && | |
| !transform_feedback_varyings.empty()) { | |
| return; | |
| } | |
| GLenum format; | |
| GLsizei length = 0; | |
| glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &length); | |
| if (length == 0 || | |
| static_cast<unsigned int>(length) > GetCurrentMaxSizeBytes()) { | |
| return; | |
| } | |
| std::vector<uint8_t> binary(length); | |
| glGetProgramBinary(program, length, nullptr, &format, | |
| reinterpret_cast<char*>(binary.data())); | |
| if (compress_program_binaries_) { | |
| binary = CompressData(binary); | |
| } | |
| // If the binary is so big it will never fit in the cache, throw it away. | |
| if (binary.size() > GetCurrentMaxSizeBytes()) { | |
| return; | |
| } | |
| Hash a_sha; | |
| Hash b_sha; | |
| DCHECK(shader_a && !shader_a->last_compiled_source().empty() && | |
| shader_b && !shader_b->last_compiled_source().empty()); | |
| ComputeShaderHash( | |
| shader_a->last_compiled_signature(), a_sha); | |
| ComputeShaderHash( | |
| shader_b->last_compiled_signature(), b_sha); | |
| Hash program_sha; | |
| ComputeProgramHash(a_sha, b_sha, bind_attrib_location_map, | |
| transform_feedback_varyings, | |
| transform_feedback_buffer_mode, program_sha); | |
| // Evict any cached program with the same key in favor of the least recently | |
| // accessed. | |
| ProgramLRUCache::iterator existing = store_.Peek(program_sha); | |
| if(existing != store_.end()) | |
| store_.Erase(existing); | |
| // If the cache is overflowing, remove some old entries. | |
| DCHECK(GetCurrentMaxSizeBytes() >= binary.size()); | |
| Trim(GetCurrentMaxSizeBytes() - binary.size()); | |
| if (!disable_gpu_shader_disk_cache_) { | |
| std::unique_ptr<GpuProgramProto> proto( | |
| GpuProgramProto::default_instance().New()); | |
| proto->set_sha(program_sha.data(), program_sha.size()); | |
| proto->set_format(format); | |
| proto->set_program(binary.data(), binary.size()); | |
| proto->set_program_decompressed_length(length); | |
| proto->set_program_is_compressed(compress_program_binaries_); | |
| FillShaderProto(proto->mutable_vertex_shader(), a_sha, shader_a); | |
| FillShaderProto(proto->mutable_fragment_shader(), b_sha, shader_b); | |
| RunShaderCallback(client, proto.get(), program_sha); | |
| } | |
| store_.Put( | |
| program_sha, | |
| base::MakeRefCounted<ProgramCacheValue>( | |
| format, std::move(binary), compress_program_binaries_, length, | |
| program_sha, a_sha, shader_a->attrib_map(), shader_a->uniform_map(), | |
| shader_a->varying_map(), shader_a->output_variable_list(), | |
| shader_a->interface_block_map(), b_sha, shader_b->attrib_map(), | |
| shader_b->uniform_map(), shader_b->varying_map(), | |
| shader_b->output_variable_list(), shader_b->interface_block_map(), | |
| this)); | |
| } | |
| void MemoryProgramCache::LoadProgram(const std::string& key, | |
| const std::string& program) { | |
| std::unique_ptr<GpuProgramProto> proto( | |
| GpuProgramProto::default_instance().New()); | |
| if (!proto->ParseFromString(program)) { | |
| DVLOG(2) << "Failed to parse proto file."; | |
| return; | |
| } | |
| AttributeMap vertex_attribs; | |
| UniformMap vertex_uniforms; | |
| VaryingMap vertex_varyings; | |
| OutputVariableList vertex_output_variables; | |
| InterfaceBlockMap vertex_interface_blocks; | |
| for (int i = 0; i < proto->vertex_shader().attribs_size(); i++) { | |
| RetrieveShaderAttributeInfo(proto->vertex_shader().attribs(i), | |
| &vertex_attribs); | |
| } | |
| for (int i = 0; i < proto->vertex_shader().uniforms_size(); i++) { | |
| RetrieveShaderUniformInfo(proto->vertex_shader().uniforms(i), | |
| &vertex_uniforms); | |
| } | |
| for (int i = 0; i < proto->vertex_shader().varyings_size(); i++) { | |
| RetrieveShaderVaryingInfo(proto->vertex_shader().varyings(i), | |
| &vertex_varyings); | |
| } | |
| for (int i = 0; i < proto->vertex_shader().output_variables_size(); i++) { | |
| RetrieveShaderOutputVariableInfo(proto->vertex_shader().output_variables(i), | |
| &vertex_output_variables); | |
| } | |
| for (int i = 0; i < proto->vertex_shader().interface_blocks_size(); i++) { | |
| RetrieveShaderInterfaceBlockInfo(proto->vertex_shader().interface_blocks(i), | |
| &vertex_interface_blocks); | |
| } | |
| AttributeMap fragment_attribs; | |
| UniformMap fragment_uniforms; | |
| VaryingMap fragment_varyings; | |
| OutputVariableList fragment_output_variables; | |
| InterfaceBlockMap fragment_interface_blocks; | |
| for (int i = 0; i < proto->fragment_shader().attribs_size(); i++) { | |
| RetrieveShaderAttributeInfo(proto->fragment_shader().attribs(i), | |
| &fragment_attribs); | |
| } | |
| for (int i = 0; i < proto->fragment_shader().uniforms_size(); i++) { | |
| RetrieveShaderUniformInfo(proto->fragment_shader().uniforms(i), | |
| &fragment_uniforms); | |
| } | |
| for (int i = 0; i < proto->fragment_shader().varyings_size(); i++) { | |
| RetrieveShaderVaryingInfo(proto->fragment_shader().varyings(i), | |
| &fragment_varyings); | |
| } | |
| for (int i = 0; i < proto->fragment_shader().output_variables_size(); i++) { | |
| RetrieveShaderOutputVariableInfo( | |
| proto->fragment_shader().output_variables(i), | |
| &fragment_output_variables); | |
| } | |
| for (int i = 0; i < proto->fragment_shader().interface_blocks_size(); i++) { | |
| RetrieveShaderInterfaceBlockInfo( | |
| proto->fragment_shader().interface_blocks(i), | |
| &fragment_interface_blocks); | |
| } | |
| std::vector<uint8_t> binary(proto->program().length()); | |
| UNSAFE_TODO(memcpy(binary.data(), proto->program().c_str(), | |
| proto->program().length())); | |
| auto program_sha = | |
| base::as_byte_span(proto->sha()).to_fixed_extent<kHashLength>(); | |
| auto vertex_shader_sha = base::as_byte_span(proto->vertex_shader().sha()) | |
| .to_fixed_extent<kHashLength>(); | |
| auto fragment_shader_sha = base::as_byte_span(proto->fragment_shader().sha()) | |
| .to_fixed_extent<kHashLength>(); | |
| if (!program_sha.has_value()) { | |
| DVLOG(2) << "Ill-formed program hash"; | |
| return; | |
| } | |
| if (!vertex_shader_sha.has_value()) { | |
| DVLOG(2) << "Ill-formed vertex shader hash"; | |
| return; | |
| } | |
| if (!fragment_shader_sha.has_value()) { | |
| DVLOG(2) << "Ill-formed fragment shader hash"; | |
| return; | |
| } | |
| store_.Put( | |
| SpanToArray(program_sha.value()), | |
| base::MakeRefCounted<ProgramCacheValue>( | |
| proto->format(), std::move(binary), | |
| proto->has_program_is_compressed() && proto->program_is_compressed(), | |
| proto->program_decompressed_length(), program_sha.value(), | |
| vertex_shader_sha.value(), vertex_attribs, vertex_uniforms, | |
| vertex_varyings, vertex_output_variables, vertex_interface_blocks, | |
| fragment_shader_sha.value(), fragment_attribs, fragment_uniforms, | |
| fragment_varyings, fragment_output_variables, | |
| fragment_interface_blocks, this)); | |
| } | |
| size_t MemoryProgramCache::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_; | |
| } | |
| void MemoryProgramCache::OnUpdateMemoryLimit() { | |
| 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 MemoryProgramCache::OnReleaseMemory() { | |
| 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; | |
| Trim(current_max_size_bytes_); | |
| return; | |
| } | |
| int limit = memory_limit(); | |
| if (limit <= base::kCriticalMemoryPressureThreshold) { | |
| Trim(0); | |
| } else if (limit <= base::kModerateMemoryPressureThreshold) { | |
| Trim(max_size_bytes() / 4); | |
| } | |
| } | |
| MemoryProgramCache::ProgramCacheValue::ProgramCacheValue( | |
| GLenum format, | |
| std::vector<uint8_t> data, | |
| bool is_compressed, | |
| GLsizei decompressed_length, | |
| HashView program_hash, | |
| HashView shader_0_hash, | |
| const AttributeMap& attrib_map_0, | |
| const UniformMap& uniform_map_0, | |
| const VaryingMap& varying_map_0, | |
| const OutputVariableList& output_variable_list_0, | |
| const InterfaceBlockMap& interface_block_map_0, | |
| HashView shader_1_hash, | |
| const AttributeMap& attrib_map_1, | |
| const UniformMap& uniform_map_1, | |
| const VaryingMap& varying_map_1, | |
| const OutputVariableList& output_variable_list_1, | |
| const InterfaceBlockMap& interface_block_map_1, | |
| MemoryProgramCache* program_cache) | |
| : format_(format), | |
| data_(std::move(data)), | |
| is_compressed_(is_compressed), | |
| decompressed_length_(decompressed_length), | |
| program_hash_(SpanToArray(program_hash)), | |
| shader_0_hash_(SpanToArray(shader_0_hash)), | |
| attrib_map_0_(attrib_map_0), | |
| uniform_map_0_(uniform_map_0), | |
| varying_map_0_(varying_map_0), | |
| output_variable_list_0_(output_variable_list_0), | |
| interface_block_map_0_(interface_block_map_0), | |
| shader_1_hash_(SpanToArray(shader_1_hash)), | |
| attrib_map_1_(attrib_map_1), | |
| uniform_map_1_(uniform_map_1), | |
| varying_map_1_(varying_map_1), | |
| output_variable_list_1_(output_variable_list_1), | |
| interface_block_map_1_(interface_block_map_1), | |
| program_cache_(program_cache) { | |
| program_cache_->curr_size_bytes_ += data_.size(); | |
| program_cache->CompiledShaderCacheSuccess(shader_0_hash_); | |
| program_cache->CompiledShaderCacheSuccess(shader_1_hash_); | |
| program_cache_->LinkedProgramCacheSuccess(program_hash_); | |
| } | |
| MemoryProgramCache::ProgramCacheValue::~ProgramCacheValue() { | |
| program_cache_->curr_size_bytes_ -= data_.size(); | |
| program_cache_->Evict(program_hash_, shader_0_hash_, shader_1_hash_); | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |