// Copyright (c) 2012 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/memory_program_cache.h" #include #include "base/base64.h" #include "base/callback.h" #include "base/command_line.h" #include "base/metrics/histogram.h" #include "base/sha1.h" #include "base/strings/string_number_conversions.h" #include "gpu/command_buffer/common/constants.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/gpu_switches.h" #include "gpu/command_buffer/service/shader_manager.h" #include "ui/gl/gl_bindings.h" namespace { size_t GetCacheSizeBytes() { const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kGpuProgramCacheSizeKb)) { size_t size; if (base::StringToSizeT( command_line->GetSwitchValueNative(switches::kGpuProgramCacheSizeKb), &size)) return size * 1024; } return gpu::kDefaultMaxProgramCacheMemoryBytes; } } // anonymous namespace namespace gpu { namespace gles2 { namespace { 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.arraySize); 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.structName); } 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 FillShaderProto(ShaderProto* proto, const char* sha, const Shader* shader) { proto->set_sha(sha, gpu::gles2::ProgramCache::kHashLength); for (AttributeMap::const_iterator iter = shader->attrib_map().begin(); iter != shader->attrib_map().end(); ++iter) { ShaderAttributeProto* info = proto->add_attribs(); FillShaderAttributeProto(info, iter->second); } for (UniformMap::const_iterator iter = shader->uniform_map().begin(); iter != shader->uniform_map().end(); ++iter) { ShaderUniformProto* info = proto->add_uniforms(); FillShaderUniformProto(info, iter->second); } for (VaryingMap::const_iterator iter = shader->varying_map().begin(); iter != shader->varying_map().end(); ++iter) { ShaderVaryingProto* info = proto->add_varyings(); FillShaderVaryingProto(info, iter->second); } for (auto iter = shader->output_variable_list().begin(); iter != shader->output_variable_list().end(); ++iter) { ShaderOutputVariableProto* info = proto->add_output_variables(); FillShaderOutputVariableProto(info, *iter); } } 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->arraySize = 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->structName = 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( 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 RunShaderCallback(const ShaderCacheCallback& callback, GpuProgramProto* proto, std::string sha_string) { std::string shader; proto->SerializeToString(&shader); std::string key; base::Base64Encode(sha_string, &key); callback.Run(key, shader); } } // namespace MemoryProgramCache::MemoryProgramCache() : max_size_bytes_(GetCacheSizeBytes()), curr_size_bytes_(0), store_(ProgramMRUCache::NO_AUTO_EVICT) { } MemoryProgramCache::MemoryProgramCache(const size_t max_cache_size_bytes) : max_size_bytes_(max_cache_size_bytes), curr_size_bytes_(0), store_(ProgramMRUCache::NO_AUTO_EVICT) { } MemoryProgramCache::~MemoryProgramCache() {} void MemoryProgramCache::ClearBackend() { store_.Clear(); DCHECK_EQ(0U, curr_size_bytes_); } ProgramCache::ProgramLoadResult MemoryProgramCache::LoadLinkedProgram( GLuint program, Shader* shader_a, Shader* shader_b, const LocationMap* bind_attrib_location_map, const std::vector& transform_feedback_varyings, GLenum transform_feedback_buffer_mode, const ShaderCacheCallback& shader_callback) { char a_sha[kHashLength]; char b_sha[kHashLength]; 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); char sha[kHashLength]; ComputeProgramHash(a_sha, b_sha, bind_attrib_location_map, transform_feedback_varyings, transform_feedback_buffer_mode, sha); const std::string sha_string(sha, kHashLength); ProgramMRUCache::iterator found = store_.Get(sha_string); if (found == store_.end()) { return PROGRAM_LOAD_FAILURE; } const scoped_refptr value = found->second; glProgramBinary(program, value->format(), static_cast(value->data()), value->length()); 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_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()); if (!shader_callback.is_null() && !base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableGpuShaderDiskCache)) { scoped_ptr proto( GpuProgramProto::default_instance().New()); proto->set_sha(sha, kHashLength); proto->set_format(value->format()); proto->set_program(value->data(), value->length()); FillShaderProto(proto->mutable_vertex_shader(), a_sha, shader_a); FillShaderProto(proto->mutable_fragment_shader(), b_sha, shader_b); RunShaderCallback(shader_callback, proto.get(), sha_string); } 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& transform_feedback_varyings, GLenum transform_feedback_buffer_mode, const ShaderCacheCallback& shader_callback) { GLenum format; GLsizei length = 0; glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH_OES, &length); if (length == 0 || static_cast(length) > max_size_bytes_) { return; } scoped_ptr binary(new char[length]); glGetProgramBinary(program, length, NULL, &format, binary.get()); UMA_HISTOGRAM_COUNTS("GPU.ProgramCache.ProgramBinarySizeBytes", length); char a_sha[kHashLength]; char b_sha[kHashLength]; 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); char sha[kHashLength]; ComputeProgramHash(a_sha, b_sha, bind_attrib_location_map, transform_feedback_varyings, transform_feedback_buffer_mode, sha); const std::string sha_string(sha, sizeof(sha)); UMA_HISTOGRAM_COUNTS("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(sha_string); if(existing != store_.end()) store_.Erase(existing); while (curr_size_bytes_ + length > max_size_bytes_) { DCHECK(!store_.empty()); store_.Erase(store_.rbegin()); } if (!shader_callback.is_null() && !base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableGpuShaderDiskCache)) { scoped_ptr proto( GpuProgramProto::default_instance().New()); proto->set_sha(sha, kHashLength); proto->set_format(format); proto->set_program(binary.get(), length); FillShaderProto(proto->mutable_vertex_shader(), a_sha, shader_a); FillShaderProto(proto->mutable_fragment_shader(), b_sha, shader_b); RunShaderCallback(shader_callback, proto.get(), sha_string); } store_.Put( sha_string, new ProgramCacheValue( length, format, binary.release(), sha_string, a_sha, shader_a->attrib_map(), shader_a->uniform_map(), shader_a->varying_map(), shader_a->output_variable_list(), b_sha, shader_b->attrib_map(), shader_b->uniform_map(), shader_b->varying_map(), shader_b->output_variable_list(), this)); UMA_HISTOGRAM_COUNTS("GPU.ProgramCache.MemorySizeAfterKb", curr_size_bytes_ / 1024); } void MemoryProgramCache::LoadProgram(const std::string& program) { scoped_ptr proto(GpuProgramProto::default_instance().New()); if (proto->ParseFromString(program)) { AttributeMap vertex_attribs; UniformMap vertex_uniforms; VaryingMap vertex_varyings; OutputVariableList vertex_output_variables; 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); } AttributeMap fragment_attribs; UniformMap fragment_uniforms; VaryingMap fragment_varyings; OutputVariableList fragment_output_variables; 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); } scoped_ptr binary(new char[proto->program().length()]); memcpy(binary.get(), proto->program().c_str(), proto->program().length()); store_.Put( proto->sha(), new ProgramCacheValue( proto->program().length(), proto->format(), binary.release(), proto->sha(), proto->vertex_shader().sha().c_str(), vertex_attribs, vertex_uniforms, vertex_varyings, vertex_output_variables, proto->fragment_shader().sha().c_str(), fragment_attribs, fragment_uniforms, fragment_varyings, fragment_output_variables, this)); UMA_HISTOGRAM_COUNTS("GPU.ProgramCache.MemorySizeAfterKb", curr_size_bytes_ / 1024); } else { LOG(ERROR) << "Failed to parse proto file."; } } MemoryProgramCache::ProgramCacheValue::ProgramCacheValue( GLsizei length, GLenum format, const char* data, const std::string& program_hash, const char* 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 char* shader_1_hash, const AttributeMap& attrib_map_1, const UniformMap& uniform_map_1, const VaryingMap& varying_map_1, const OutputVariableList& output_variable_list_1, MemoryProgramCache* program_cache) : length_(length), format_(format), data_(data), program_hash_(program_hash), shader_0_hash_(shader_0_hash, kHashLength), 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), shader_1_hash_(shader_1_hash, kHashLength), 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), program_cache_(program_cache) { program_cache_->curr_size_bytes_ += length_; program_cache_->LinkedProgramCacheSuccess(program_hash); } MemoryProgramCache::ProgramCacheValue::~ProgramCacheValue() { program_cache_->curr_size_bytes_ -= length_; program_cache_->Evict(program_hash_); } } // namespace gles2 } // namespace gpu