// 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/program_manager.h" #include #include #include #include #include #include #include "base/command_line.h" #include "base/containers/hash_tables.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/numerics/safe_math.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "gpu/command_buffer/common/gles2_cmd_format.h" #include "gpu/command_buffer/common/gles2_cmd_utils.h" #include "gpu/command_buffer/service/feature_info.h" #include "gpu/command_buffer/service/gles2_cmd_decoder.h" #include "gpu/command_buffer/service/gpu_preferences.h" #include "gpu/command_buffer/service/program_cache.h" #include "gpu/command_buffer/service/shader_manager.h" #include "third_party/re2/src/re2/re2.h" #include "ui/gl/gl_version_info.h" using base::TimeDelta; using base::TimeTicks; namespace gpu { namespace gles2 { namespace { int ShaderTypeToIndex(GLenum shader_type) { switch (shader_type) { case GL_VERTEX_SHADER: return 0; case GL_FRAGMENT_SHADER: return 1; default: NOTREACHED(); return 0; } } // Given a name like "foo.bar[123].moo[456]" sets new_name to "foo.bar[123].moo" // and sets element_index to 456. returns false if element expression was not a // whole decimal number. For example: "foo[1b2]" bool GetUniformNameSansElement( const std::string& name, int* element_index, std::string* new_name) { DCHECK(element_index); DCHECK(new_name); if (name.size() < 3 || name[name.size() - 1] != ']') { *element_index = 0; *new_name = name; return true; } // Look for an array specification. size_t open_pos = name.find_last_of('['); if (open_pos == std::string::npos || open_pos >= name.size() - 2) { return false; } base::CheckedNumeric index = 0; size_t last = name.size() - 1; for (size_t pos = open_pos + 1; pos < last; ++pos) { int8_t digit = name[pos] - '0'; if (digit < 0 || digit > 9) { return false; } index = index * 10 + digit; } if (!index.IsValid()) { return false; } *element_index = index.ValueOrDie(); *new_name = name.substr(0, open_pos); return true; } bool IsBuiltInFragmentVarying(const std::string& name) { // Built-in variables for fragment shaders. const char* kBuiltInVaryings[] = { "gl_FragCoord", "gl_FrontFacing", "gl_PointCoord" }; for (size_t ii = 0; ii < arraysize(kBuiltInVaryings); ++ii) { if (name == kBuiltInVaryings[ii]) return true; } return false; } bool IsBuiltInInvariant( const VaryingMap& varyings, const std::string& name) { VaryingMap::const_iterator hit = varyings.find(name); if (hit == varyings.end()) return false; return hit->second.isInvariant; } uint32_t ComputeOffset(const void* start, const void* position) { return static_cast(position) - static_cast(start); } } // anonymous namespace. Program::UniformInfo::UniformInfo() : size(0), type(GL_NONE), accepts_api_type(0), fake_location_base(0), is_array(false) {} Program::UniformInfo::UniformInfo(const std::string& client_name, int client_location_base, GLenum _type, bool _is_array, const std::vector& service_locations) : size(service_locations.size()), type(_type), accepts_api_type(0), fake_location_base(client_location_base), is_array(_is_array), name(client_name), element_locations(service_locations) { switch (type) { case GL_INT: accepts_api_type = kUniform1i; break; case GL_INT_VEC2: accepts_api_type = kUniform2i; break; case GL_INT_VEC3: accepts_api_type = kUniform3i; break; case GL_INT_VEC4: accepts_api_type = kUniform4i; break; case GL_UNSIGNED_INT: accepts_api_type = kUniform1ui; break; case GL_UNSIGNED_INT_VEC2: accepts_api_type = kUniform2ui; break; case GL_UNSIGNED_INT_VEC3: accepts_api_type = kUniform3ui; break; case GL_UNSIGNED_INT_VEC4: accepts_api_type = kUniform4ui; break; case GL_BOOL: accepts_api_type = kUniform1i | kUniform1ui | kUniform1f; break; case GL_BOOL_VEC2: accepts_api_type = kUniform2i | kUniform2ui | kUniform2f; break; case GL_BOOL_VEC3: accepts_api_type = kUniform3i | kUniform3ui | kUniform3f; break; case GL_BOOL_VEC4: accepts_api_type = kUniform4i | kUniform4ui | kUniform4f; break; case GL_FLOAT: accepts_api_type = kUniform1f; break; case GL_FLOAT_VEC2: accepts_api_type = kUniform2f; break; case GL_FLOAT_VEC3: accepts_api_type = kUniform3f; break; case GL_FLOAT_VEC4: accepts_api_type = kUniform4f; break; case GL_FLOAT_MAT2: accepts_api_type = kUniformMatrix2f; break; case GL_FLOAT_MAT3: accepts_api_type = kUniformMatrix3f; break; case GL_FLOAT_MAT4: accepts_api_type = kUniformMatrix4f; break; case GL_FLOAT_MAT2x3: accepts_api_type = kUniformMatrix2x3f; break; case GL_FLOAT_MAT2x4: accepts_api_type = kUniformMatrix2x4f; break; case GL_FLOAT_MAT3x2: accepts_api_type = kUniformMatrix3x2f; break; case GL_FLOAT_MAT3x4: accepts_api_type = kUniformMatrix3x4f; break; case GL_FLOAT_MAT4x2: accepts_api_type = kUniformMatrix4x2f; break; case GL_FLOAT_MAT4x3: accepts_api_type = kUniformMatrix4x3f; break; case GL_SAMPLER_2D: case GL_SAMPLER_2D_RECT_ARB: case GL_SAMPLER_CUBE: case GL_SAMPLER_3D_OES: case GL_SAMPLER_EXTERNAL_OES: case GL_SAMPLER_2D_ARRAY: case GL_SAMPLER_2D_SHADOW: case GL_SAMPLER_2D_ARRAY_SHADOW: case GL_SAMPLER_CUBE_SHADOW: case GL_INT_SAMPLER_2D: case GL_INT_SAMPLER_3D: case GL_INT_SAMPLER_CUBE: case GL_INT_SAMPLER_2D_ARRAY: case GL_UNSIGNED_INT_SAMPLER_2D: case GL_UNSIGNED_INT_SAMPLER_3D: case GL_UNSIGNED_INT_SAMPLER_CUBE: case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: accepts_api_type = kUniform1i; break; default: NOTREACHED() << "Unhandled UniformInfo type " << type; break; } DCHECK_LT(0, size); DCHECK(is_array || size == 1); size_t num_texture_units = IsSampler() ? static_cast(size) : 0u; texture_units.clear(); texture_units.resize(num_texture_units, 0); } Program::UniformInfo::UniformInfo(const UniformInfo& other) = default; Program::UniformInfo::~UniformInfo() {} bool ProgramManager::HasBuiltInPrefix(const std::string& name) { return name.length() >= 3 && name[0] == 'g' && name[1] == 'l' && name[2] == '_'; } Program::Program(ProgramManager* manager, GLuint service_id) : manager_(manager), use_count_(0), max_attrib_name_length_(0), max_uniform_name_length_(0), service_id_(service_id), deleted_(false), valid_(false), link_status_(false), uniforms_cleared_(false), transform_feedback_buffer_mode_(GL_NONE) { manager_->StartTracking(this); } void Program::Reset() { valid_ = false; link_status_ = false; max_uniform_name_length_ = 0; max_attrib_name_length_ = 0; attrib_infos_.clear(); uniform_infos_.clear(); uniform_locations_.clear(); fragment_input_infos_.clear(); fragment_input_locations_.clear(); program_output_infos_.clear(); sampler_indices_.clear(); attrib_location_to_index_map_.clear(); } std::string Program::ProcessLogInfo( const std::string& log) { std::string output; re2::StringPiece input(log); std::string prior_log; std::string hashed_name; while (RE2::Consume(&input, "(.*?)(webgl_[0123456789abcdefABCDEF]+)", &prior_log, &hashed_name)) { output += prior_log; const std::string* original_name = GetOriginalNameFromHashedName(hashed_name); if (original_name) output += *original_name; else output += hashed_name; } return output + input.as_string(); } void Program::UpdateLogInfo() { GLint max_len = 0; glGetProgramiv(service_id_, GL_INFO_LOG_LENGTH, &max_len); if (max_len == 0) { set_log_info(NULL); return; } scoped_ptr temp(new char[max_len]); GLint len = 0; glGetProgramInfoLog(service_id_, max_len, &len, temp.get()); DCHECK(max_len == 0 || len < max_len); DCHECK(len == 0 || temp[len] == '\0'); std::string log(temp.get(), len); set_log_info(ProcessLogInfo(log).c_str()); } void Program::ClearUniforms(std::vector* zero_buffer) { DCHECK(zero_buffer); if (uniforms_cleared_) { return; } uniforms_cleared_ = true; for (const UniformInfo& uniform_info : uniform_infos_) { GLint location = uniform_info.element_locations[0]; GLsizei size = uniform_info.size; uint32_t unit_size = GLES2Util::GetElementCountForUniformType(uniform_info.type) * GLES2Util::GetElementSizeForUniformType(uniform_info.type); DCHECK_LT(0u, unit_size); uint32_t size_needed = size * unit_size; if (size_needed > zero_buffer->size()) { zero_buffer->resize(size_needed, 0u); } const void* zero = &(*zero_buffer)[0]; switch (uniform_info.type) { case GL_FLOAT: glUniform1fv(location, size, reinterpret_cast(zero)); break; case GL_FLOAT_VEC2: glUniform2fv(location, size, reinterpret_cast(zero)); break; case GL_FLOAT_VEC3: glUniform3fv(location, size, reinterpret_cast(zero)); break; case GL_FLOAT_VEC4: glUniform4fv(location, size, reinterpret_cast(zero)); break; case GL_INT: case GL_BOOL: case GL_SAMPLER_2D: case GL_SAMPLER_CUBE: case GL_SAMPLER_EXTERNAL_OES: // extension. case GL_SAMPLER_2D_RECT_ARB: // extension. glUniform1iv(location, size, reinterpret_cast(zero)); break; case GL_INT_VEC2: case GL_BOOL_VEC2: glUniform2iv(location, size, reinterpret_cast(zero)); break; case GL_INT_VEC3: case GL_BOOL_VEC3: glUniform3iv(location, size, reinterpret_cast(zero)); break; case GL_INT_VEC4: case GL_BOOL_VEC4: glUniform4iv(location, size, reinterpret_cast(zero)); break; case GL_FLOAT_MAT2: glUniformMatrix2fv( location, size, false, reinterpret_cast(zero)); break; case GL_FLOAT_MAT3: glUniformMatrix3fv( location, size, false, reinterpret_cast(zero)); break; case GL_FLOAT_MAT4: glUniformMatrix4fv( location, size, false, reinterpret_cast(zero)); break; // ES3 types. case GL_UNSIGNED_INT: glUniform1uiv(location, size, reinterpret_cast(zero)); break; case GL_SAMPLER_3D: case GL_SAMPLER_2D_SHADOW: case GL_SAMPLER_2D_ARRAY: case GL_SAMPLER_2D_ARRAY_SHADOW: case GL_SAMPLER_CUBE_SHADOW: case GL_INT_SAMPLER_2D: case GL_INT_SAMPLER_3D: case GL_INT_SAMPLER_CUBE: case GL_INT_SAMPLER_2D_ARRAY: case GL_UNSIGNED_INT_SAMPLER_2D: case GL_UNSIGNED_INT_SAMPLER_3D: case GL_UNSIGNED_INT_SAMPLER_CUBE: case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: glUniform1iv(location, size, reinterpret_cast(zero)); break; case GL_UNSIGNED_INT_VEC2: glUniform2uiv(location, size, reinterpret_cast(zero)); break; case GL_UNSIGNED_INT_VEC3: glUniform3uiv(location, size, reinterpret_cast(zero)); break; case GL_UNSIGNED_INT_VEC4: glUniform4uiv(location, size, reinterpret_cast(zero)); break; case GL_FLOAT_MAT2x3: glUniformMatrix2x3fv( location, size, false, reinterpret_cast(zero)); break; case GL_FLOAT_MAT3x2: glUniformMatrix3x2fv( location, size, false, reinterpret_cast(zero)); break; case GL_FLOAT_MAT2x4: glUniformMatrix2x4fv( location, size, false, reinterpret_cast(zero)); break; case GL_FLOAT_MAT4x2: glUniformMatrix4x2fv( location, size, false, reinterpret_cast(zero)); break; case GL_FLOAT_MAT3x4: glUniformMatrix3x4fv( location, size, false, reinterpret_cast(zero)); break; case GL_FLOAT_MAT4x3: glUniformMatrix4x3fv( location, size, false, reinterpret_cast(zero)); break; default: NOTREACHED(); break; } } } void Program::Update() { Reset(); UpdateLogInfo(); link_status_ = true; uniforms_cleared_ = false; GLint num_attribs = 0; GLint max_len = 0; GLint max_location = -1; glGetProgramiv(service_id_, GL_ACTIVE_ATTRIBUTES, &num_attribs); glGetProgramiv(service_id_, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &max_len); // TODO(gman): Should we check for error? scoped_ptr name_buffer(new char[max_len]); for (GLint ii = 0; ii < num_attribs; ++ii) { GLsizei length = 0; GLsizei size = 0; GLenum type = 0; glGetActiveAttrib( service_id_, ii, max_len, &length, &size, &type, name_buffer.get()); DCHECK(max_len == 0 || length < max_len); DCHECK(length == 0 || name_buffer[length] == '\0'); std::string original_name; GetVertexAttribData(name_buffer.get(), &original_name, &type); // TODO(gman): Should we check for error? GLint location = glGetAttribLocation(service_id_, name_buffer.get()); if (location > max_location) { max_location = location; } attrib_infos_.push_back(VertexAttrib(1, type, original_name, location)); max_attrib_name_length_ = std::max( max_attrib_name_length_, static_cast(original_name.size())); } // Create attrib location to index map. attrib_location_to_index_map_.resize(max_location + 1); for (GLint ii = 0; ii <= max_location; ++ii) { attrib_location_to_index_map_[ii] = -1; } for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) { const VertexAttrib& info = attrib_infos_[ii]; attrib_location_to_index_map_[info.location] = ii; } #if !defined(NDEBUG) if (manager_->gpu_preferences_.enable_gpu_service_logging_gpu) { DVLOG(1) << "----: attribs for service_id: " << service_id(); for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) { const VertexAttrib& info = attrib_infos_[ii]; DVLOG(1) << ii << ": loc = " << info.location << ", size = " << info.size << ", type = " << GLES2Util::GetStringEnum(info.type) << ", name = " << info.name; } } #endif UpdateUniforms(); #if !defined(NDEBUG) if (manager_->gpu_preferences_.enable_gpu_service_logging_gpu) { DVLOG(1) << "----: uniforms for service_id: " << service_id(); size_t ii = 0; for (const UniformInfo& info : uniform_infos_) { DVLOG(1) << ii++ << ": loc = " << info.element_locations[0] << ", size = " << info.size << ", type = " << GLES2Util::GetStringEnum(info.type) << ", name = " << info.name; } } #endif UpdateFragmentInputs(); UpdateProgramOutputs(); valid_ = true; } void Program::UpdateUniforms() { // Reserve each client-bound uniform location. This way unbound uniforms will // not be allocated to locations that user expects bound uniforms to be, even // if the expected uniforms are optimized away by the driver. for (const auto& binding : bind_uniform_location_map_) { if (binding.second < 0) continue; size_t client_location = static_cast(binding.second); if (uniform_locations_.size() <= client_location) uniform_locations_.resize(client_location + 1); uniform_locations_[client_location].SetInactive(); } GLint num_uniforms = 0; glGetProgramiv(service_id_, GL_ACTIVE_UNIFORMS, &num_uniforms); if (num_uniforms <= 0) return; uniform_infos_.resize(num_uniforms); GLint name_buffer_length = 0; glGetProgramiv(service_id_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &name_buffer_length); DCHECK(name_buffer_length > 0); scoped_ptr name_buffer(new char[name_buffer_length]); size_t unused_client_location_cursor = 0; for (GLint uniform_index = 0; uniform_index < num_uniforms; ++uniform_index) { GLsizei name_length = 0; GLsizei size = 0; GLenum type = GL_NONE; glGetActiveUniform(service_id_, uniform_index, name_buffer_length, &name_length, &size, &type, name_buffer.get()); DCHECK(name_length < name_buffer_length); DCHECK(name_length == 0 || name_buffer[name_length] == '\0'); std::string service_name(name_buffer.get(), name_length); GLint service_location = -1; // Force builtin uniforms (gl_DepthRange) to have invalid location. if (!ProgramManager::HasBuiltInPrefix(service_name)) { service_location = glGetUniformLocation(service_id_, service_name.c_str()); } // Determine the client name of the uniform and whether it is an array // or not. bool is_array = false; std::string client_name; for (size_t i = 0; i < kMaxAttachedShaders && client_name.empty(); ++i) { const auto& shader = attached_shaders_[i]; if (!shader) continue; const sh::ShaderVariable* info = nullptr; const sh::Uniform* uniform = shader->GetUniformInfo(service_name); if (uniform && uniform->findInfoByMappedName(service_name, &info, &client_name)) { DCHECK(!client_name.empty()); is_array = info->arraySize > 0; type = info->type; size = std::max(1u, info->arraySize); } else { const InterfaceBlockMap& interface_block_map = shader->interface_block_map(); for (const auto& key_value : interface_block_map) { const sh::InterfaceBlock& block = key_value.second; bool find = false; if (block.instanceName.empty()) { for (const auto& value : block.fields) { if (value.findInfoByMappedName(service_name, &info, &client_name)) { find = true; break; } } } else { size_t pos = service_name.find_first_of('.'); std::string top_variable_name = service_name.substr(0, pos); if (block.mappedName == top_variable_name) { DCHECK(pos != std::string::npos); for (const auto& field : block.fields) { if (field.findInfoByMappedName(service_name.substr( pos + 1), &info, &client_name)) { find = true; client_name = block.name + "." + client_name; break; } } } } if (find) { DCHECK(!client_name.empty()); is_array = info->arraySize > 0; type = info->type; size = std::max(1u, info->arraySize); break; } } } } if (client_name.empty()) { // This happens only in cases where we do not have ANGLE or run unit tests // (or ANGLE has a severe bug). client_name = service_name; GLSLArrayName parsed_service_name(service_name); is_array = size > 1 || parsed_service_name.IsArrayName(); } std::string service_base_name = service_name; std::string client_base_name = client_name; if (is_array) { // Some drivers incorrectly return an uniform name of size-1 array without // "[0]". In this case, we correct the service name by appending "[0]" to // it. GLSLArrayName parsed_service_name(service_name); if (parsed_service_name.IsArrayName()) { service_base_name = parsed_service_name.base_name(); GLSLArrayName parsed_client_name(client_name); client_base_name = parsed_client_name.base_name(); } else { service_name += "[0]"; client_name += "[0]"; } } // Assign a location for the uniform: use either client-bound // location or automatically assigned to an unused location. size_t client_location_base = 0; LocationMap::const_iterator it = bind_uniform_location_map_.find(client_base_name); if (it != bind_uniform_location_map_.end()) { client_location_base = it->second; } else { while (unused_client_location_cursor < uniform_locations_.size() && !uniform_locations_[unused_client_location_cursor].IsUnused()) unused_client_location_cursor++; if (unused_client_location_cursor == uniform_locations_.size()) uniform_locations_.resize(unused_client_location_cursor + 1); client_location_base = unused_client_location_cursor; unused_client_location_cursor++; } // Populate the uniform list entry. std::vector service_locations; service_locations.resize(size); service_locations[0] = service_location; if (size > 1) { for (GLsizei ii = 1; ii < size; ++ii) { std::string element_name(service_base_name + "[" + base::IntToString(ii) + "]"); service_locations[ii] = glGetUniformLocation(service_id_, element_name.c_str()); } } UniformInfo& info = uniform_infos_[uniform_index]; info = UniformInfo(client_name, client_location_base, type, is_array, service_locations); if (info.IsSampler()) { sampler_indices_.push_back(uniform_index); } // Populate the uniform location list entry. // Before linking, we already validated that no two statically used uniforms // are bound to the same location. DCHECK(!uniform_locations_[client_location_base].IsActive()); uniform_locations_[client_location_base].SetActive(&info); max_uniform_name_length_ = std::max(max_uniform_name_length_, static_cast(info.name.size())); } } void Program::UpdateFragmentInputs() { if (!feature_info().feature_flags().chromium_path_rendering) return; for (const auto& binding : bind_fragment_input_location_map_) { if (binding.second < 0) continue; size_t client_location = static_cast(binding.second); if (fragment_input_locations_.size() <= client_location) fragment_input_locations_.resize(client_location + 1); fragment_input_locations_[client_location].SetInactive(); } GLint num_fragment_inputs = 0; glGetProgramInterfaceiv(service_id_, GL_FRAGMENT_INPUT_NV, GL_ACTIVE_RESOURCES, &num_fragment_inputs); if (num_fragment_inputs <= 0) return; GLint max_len = 0; glGetProgramInterfaceiv(service_id_, GL_FRAGMENT_INPUT_NV, GL_MAX_NAME_LENGTH, &max_len); DCHECK(max_len > 0); scoped_ptr name_buffer(new char[max_len]); Shader* fragment_shader = attached_shaders_[ShaderTypeToIndex(GL_FRAGMENT_SHADER)].get(); const GLenum kQueryProperties[] = {GL_LOCATION, GL_TYPE, GL_ARRAY_SIZE}; std::vector client_location_indices; for (GLint ii = 0; ii < num_fragment_inputs; ++ii) { GLsizei name_length = 0; glGetProgramResourceName(service_id_, GL_FRAGMENT_INPUT_NV, ii, max_len, &name_length, name_buffer.get()); DCHECK(name_length < max_len); DCHECK(name_length == 0 || name_buffer[name_length] == '\0'); // A fragment shader can have gl_FragCoord, gl_FrontFacing or gl_PointCoord // built-ins as its input, as well as custom varyings. We are interested in // custom varyings, client is allowed to bind only them. std::string service_name(name_buffer.get(), name_length); if (ProgramManager::HasBuiltInPrefix(service_name)) continue; // Unlike when binding uniforms, we expect the driver to give correct // names: "name" for simple variable, "name[0]" for an array. GLsizei query_length = 0; GLint query_results[arraysize(kQueryProperties)] = { 0, }; glGetProgramResourceiv(service_id_, GL_FRAGMENT_INPUT_NV, ii, arraysize(kQueryProperties), kQueryProperties, arraysize(query_results), &query_length, query_results); DCHECK(query_length == arraysize(kQueryProperties)); GLenum type = static_cast(query_results[1]); GLsizei size = static_cast(query_results[2]); std::string client_name; const sh::Varying* varying = fragment_shader->GetVaryingInfo(service_name); const sh::ShaderVariable* info = nullptr; if (varying && varying->findInfoByMappedName(service_name, &info, &client_name)) { type = info->type; size = std::max(1u, info->arraySize); } else { // Should only happen if there are major bugs in the driver, ANGLE or if // the shader translator is disabled. DCHECK(feature_info().disable_shader_translator()); client_name = service_name; if (size <= 0) continue; } auto it = bind_fragment_input_location_map_.find(client_name); if (it != bind_fragment_input_location_map_.end() && it->second >= 0 && query_results[0] >= 0) { size_t client_location = static_cast(it->second); GLuint service_location = static_cast(query_results[0]); fragment_input_infos_.push_back( FragmentInputInfo(type, service_location)); client_location_indices.push_back(client_location); } if (size <= 1) continue; GLSLArrayName parsed_client_name(client_name); GLSLArrayName parsed_service_name(service_name); if (!parsed_client_name.IsArrayName() || parsed_client_name.element_index() != 0 || !parsed_service_name.IsArrayName() || parsed_service_name.element_index() != 0) { NOTREACHED() << "GLSL array variable names should end with \"[0]\". " "Likely driver or ANGLE error."; continue; } for (GLsizei jj = 1; jj < size; ++jj) { std::string array_spec(std::string("[") + base::IntToString(jj) + "]"); std::string client_element_name = parsed_client_name.base_name() + array_spec; auto it = bind_fragment_input_location_map_.find(client_element_name); if (it != bind_fragment_input_location_map_.end() && it->second >= 0) { size_t client_location = static_cast(it->second); std::string service_element_name = parsed_service_name.base_name() + array_spec; GLint service_location = glGetProgramResourceLocation( service_id_, GL_FRAGMENT_INPUT_NV, service_element_name.c_str()); if (service_location >= 0) { fragment_input_infos_.push_back( FragmentInputInfo(type, static_cast(service_location))); client_location_indices.push_back(client_location); } } } } for (size_t i = 0; i < client_location_indices.size(); ++i) { size_t client_location = client_location_indices[i]; // Before linking, we already validated that no two statically used fragment // inputs are bound to the same location. DCHECK(!fragment_input_locations_[client_location].IsActive()); fragment_input_locations_[client_location].SetActive( &fragment_input_infos_[i]); } } void Program::UpdateProgramOutputs() { if (!feature_info().gl_version_info().IsES3Capable() || feature_info().disable_shader_translator()) return; Shader* fragment_shader = attached_shaders_[ShaderTypeToIndex(GL_FRAGMENT_SHADER)].get(); for (auto const& output_var : fragment_shader->output_variable_list()) { const std::string& service_name = output_var.mappedName; // A fragment shader can have gl_FragColor, gl_SecondaryFragColor, etc // built-ins as its output, as well as custom varyings. We are interested // only in custom varyings, client is allowed to bind only them. if (ProgramManager::HasBuiltInPrefix(service_name)) continue; std::string client_name = output_var.name; if (output_var.arraySize == 0) { GLint color_name = glGetFragDataLocation(service_id_, service_name.c_str()); if (color_name < 0) continue; GLint index = 0; if (feature_info().feature_flags().ext_blend_func_extended) index = glGetFragDataIndex(service_id_, service_name.c_str()); if (index < 0) continue; program_output_infos_.push_back( ProgramOutputInfo(color_name, index, client_name)); } else { for (size_t ii = 0; ii < output_var.arraySize; ++ii) { std::string array_spec(std::string("[") + base::IntToString(ii) + "]"); std::string service_element_name(service_name + array_spec); GLint color_name = glGetFragDataLocation(service_id_, service_element_name.c_str()); if (color_name < 0) continue; GLint index = 0; if (feature_info().feature_flags().ext_blend_func_extended) index = glGetFragDataIndex(service_id_, service_element_name.c_str()); if (index < 0) continue; program_output_infos_.push_back( ProgramOutputInfo(color_name, index, client_name + array_spec)); } } } } void Program::ExecuteBindAttribLocationCalls() { for (const auto& key_value : bind_attrib_location_map_) { const std::string* mapped_name = GetAttribMappedName(key_value.first); if (mapped_name) glBindAttribLocation(service_id_, key_value.second, mapped_name->c_str()); } } bool Program::ExecuteTransformFeedbackVaryingsCall() { if (!transform_feedback_varyings_.empty()) { Shader* vertex_shader = attached_shaders_[0].get(); if (!vertex_shader) { set_log_info("TransformFeedbackVaryings: missing vertex shader"); return false; } std::vector mapped_names; mapped_names.reserve(transform_feedback_varyings_.size()); for (StringVector::const_iterator it = transform_feedback_varyings_.begin(); it != transform_feedback_varyings_.end(); ++it) { const std::string& orig = *it; const std::string* mapped = vertex_shader->GetVaryingMappedName(orig); if (!mapped) { std::string log = "TransformFeedbackVaryings: no varying named " + orig; set_log_info(log.c_str()); return false; } mapped_names.push_back(mapped->c_str()); } glTransformFeedbackVaryings(service_id_, mapped_names.size(), &mapped_names.front(), transform_feedback_buffer_mode_); } return true; } void Program::ExecuteProgramOutputBindCalls() { if (feature_info().disable_shader_translator()) { return; } Shader* fragment_shader = attached_shaders_[ShaderTypeToIndex(GL_FRAGMENT_SHADER)].get(); DCHECK(fragment_shader && fragment_shader->valid()); if (fragment_shader->shader_version() != 100) { // ES SL 1.00 does not have mechanism for introducing variables that could // be bound. This means that ES SL 1.00 binding calls would be to // non-existing variable names. Binding calls are only executed with ES SL // 3.00 and higher. for (auto const& output_var : fragment_shader->output_variable_list()) { size_t count = std::max(output_var.arraySize, 1u); bool is_array = output_var.arraySize > 0; for (size_t jj = 0; jj < count; ++jj) { std::string name = output_var.name; std::string array_spec; if (is_array) { array_spec = std::string("[") + base::IntToString(jj) + "]"; name += array_spec; } auto it = bind_program_output_location_index_map_.find(name); if (it == bind_program_output_location_index_map_.end()) continue; std::string mapped_name = output_var.mappedName; if (is_array) { mapped_name += array_spec; } const auto& binding = it->second; if (binding.second == 0) { // Handles the cases where client called glBindFragDataLocation as // well as glBindFragDataLocationIndexed with index == 0. glBindFragDataLocation(service_id_, binding.first, mapped_name.c_str()); } else { DCHECK(feature_info().feature_flags().ext_blend_func_extended); glBindFragDataLocationIndexed(service_id_, binding.first, binding.second, mapped_name.c_str()); } } } return; } // Support for EXT_blend_func_extended when used with ES SL 1.00 client // shader. if (feature_info().gl_version_info().is_es || !feature_info().feature_flags().ext_blend_func_extended) return; // The underlying context does not support EXT_blend_func_extended // natively, need to emulate it. // ES SL 1.00 is the only language which contains GLSL built-ins // that need to be bound to color indices. If clients use other // languages, they also bind the output variables themselves. // Map gl_SecondaryFragColorEXT / gl_SecondaryFragDataEXT of // EXT_blend_func_extended to real color indexes. for (auto const& output_var : fragment_shader->output_variable_list()) { const std::string& name = output_var.mappedName; if (name == "gl_FragColor") { DCHECK_EQ(-1, output_var.location); DCHECK_EQ(0u, output_var.arraySize); // We leave these unbound by not giving a binding name. The driver will // bind this. } else if (name == "gl_FragData") { DCHECK_EQ(-1, output_var.location); DCHECK_NE(0u, output_var.arraySize); // We leave these unbound by not giving a binding name. The driver will // bind this. } else if (name == "gl_SecondaryFragColorEXT") { DCHECK_EQ(-1, output_var.location); DCHECK_EQ(0u, output_var.arraySize); glBindFragDataLocationIndexed(service_id_, 0, 1, "angle_SecondaryFragColor"); } else if (name == "gl_SecondaryFragDataEXT") { DCHECK_EQ(-1, output_var.location); DCHECK_NE(0u, output_var.arraySize); glBindFragDataLocationIndexed(service_id_, 0, 1, "angle_SecondaryFragData"); } } } bool Program::Link(ShaderManager* manager, Program::VaryingsPackingOption varyings_packing_option, const ShaderCacheCallback& shader_callback) { ClearLinkStatus(); if (!AttachedShadersExist()) { set_log_info("missing shaders"); return false; } TimeTicks before_time = TimeTicks::Now(); bool link = true; ProgramCache* cache = manager_->program_cache_; if (cache) { DCHECK(!attached_shaders_[0]->last_compiled_source().empty() && !attached_shaders_[1]->last_compiled_source().empty()); ProgramCache::LinkedProgramStatus status = cache->GetLinkedProgramStatus( attached_shaders_[0]->last_compiled_signature(), attached_shaders_[1]->last_compiled_signature(), &bind_attrib_location_map_, transform_feedback_varyings_, transform_feedback_buffer_mode_); bool cache_hit = status == ProgramCache::LINK_SUCCEEDED; UMA_HISTOGRAM_BOOLEAN("GPU.ProgramCache.CacheHit", cache_hit); if (cache_hit) { ProgramCache::ProgramLoadResult success = cache->LoadLinkedProgram(service_id(), attached_shaders_[0].get(), attached_shaders_[1].get(), &bind_attrib_location_map_, transform_feedback_varyings_, transform_feedback_buffer_mode_, shader_callback); link = success != ProgramCache::PROGRAM_LOAD_SUCCESS; UMA_HISTOGRAM_BOOLEAN("GPU.ProgramCache.LoadBinarySuccess", !link); } } if (link) { CompileAttachedShaders(); if (!CanLink()) { set_log_info("invalid shaders"); return false; } if (DetectShaderVersionMismatch()) { set_log_info("Versions of linked shaders have to match."); return false; } if (DetectAttribLocationBindingConflicts()) { set_log_info("glBindAttribLocation() conflicts"); return false; } std::string conflicting_name; if (DetectUniformsMismatch(&conflicting_name)) { std::string info_log = "Uniforms with the same name but different " "type/precision: " + conflicting_name; set_log_info(ProcessLogInfo(info_log).c_str()); return false; } if (DetectUniformLocationBindingConflicts()) { set_log_info("glBindUniformLocationCHROMIUM() conflicts"); return false; } if (DetectVaryingsMismatch(&conflicting_name)) { std::string info_log = "Varyings with the same name but different type, " "or statically used varyings in fragment shader " "are not declared in vertex shader: " + conflicting_name; set_log_info(ProcessLogInfo(info_log).c_str()); return false; } if (DetectFragmentInputLocationBindingConflicts()) { set_log_info("glBindFragmentInputLocationCHROMIUM() conflicts"); return false; } if (DetectProgramOutputLocationBindingConflicts()) { set_log_info("glBindFragDataLocation() conflicts"); return false; } if (DetectBuiltInInvariantConflicts()) { set_log_info("Invariant settings for certain built-in varyings " "have to match"); return false; } if (DetectGlobalNameConflicts(&conflicting_name)) { std::string info_log = "Name conflicts between an uniform and an " "attribute: " + conflicting_name; set_log_info(ProcessLogInfo(info_log).c_str()); return false; } if (!CheckVaryingsPacking(varyings_packing_option)) { set_log_info("Varyings over maximum register limit"); return false; } ExecuteBindAttribLocationCalls(); if (!ExecuteTransformFeedbackVaryingsCall()) { return false; } ExecuteProgramOutputBindCalls(); before_time = TimeTicks::Now(); if (cache && gfx::g_driver_gl.ext.b_GL_ARB_get_program_binary) { glProgramParameteri(service_id(), PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); } glLinkProgram(service_id()); } GLint success = 0; glGetProgramiv(service_id(), GL_LINK_STATUS, &success); if (success == GL_TRUE) { Update(); if (link) { // ANGLE updates the translated shader sources on link. for (auto shader : attached_shaders_) { shader->RefreshTranslatedShaderSource(); } if (cache) { cache->SaveLinkedProgram(service_id(), attached_shaders_[0].get(), attached_shaders_[1].get(), &bind_attrib_location_map_, transform_feedback_varyings_, transform_feedback_buffer_mode_, shader_callback); } UMA_HISTOGRAM_CUSTOM_COUNTS( "GPU.ProgramCache.BinaryCacheMissTime", static_cast( (TimeTicks::Now() - before_time).InMicroseconds()), 0, static_cast( TimeDelta::FromSeconds(10).InMicroseconds()), 50); } else { UMA_HISTOGRAM_CUSTOM_COUNTS( "GPU.ProgramCache.BinaryCacheHitTime", static_cast( (TimeTicks::Now() - before_time).InMicroseconds()), 0, static_cast( TimeDelta::FromSeconds(1).InMicroseconds()), 50); } } else { UpdateLogInfo(); } return success == GL_TRUE; } void Program::Validate() { if (!IsValid()) { set_log_info("program not linked"); return; } glValidateProgram(service_id()); UpdateLogInfo(); } GLint Program::GetUniformFakeLocation( const std::string& name) const { GLSLArrayName parsed_name(name); for (const UniformInfo& info : uniform_infos_) { if (info.name == name || (info.is_array && info.name.compare(0, info.name.size() - 3, name) == 0)) { return info.fake_location_base; } else if (parsed_name.IsArrayName() && info.is_array) { // Look for an array specification. size_t open_pos = info.name.find_last_of('['); if (info.name.compare(0, open_pos, parsed_name.base_name()) == 0) { int index = parsed_name.element_index(); if (index < info.size) { DCHECK_GT(static_cast(info.element_locations.size()), index); if (info.element_locations[index] == -1) return -1; return ProgramManager::MakeFakeLocation( info.fake_location_base, index); } } } } return -1; } GLint Program::GetAttribLocation( const std::string& original_name) const { for (GLuint ii = 0; ii < attrib_infos_.size(); ++ii) { const VertexAttrib& info = attrib_infos_[ii]; if (info.name == original_name) { return info.location; } } return -1; } const Program::UniformInfo* Program::GetUniformInfoByFakeLocation( GLint fake_location, GLint* real_location, GLint* array_index) const { DCHECK(real_location); DCHECK(array_index); if (fake_location < 0) return nullptr; size_t location_index = GetUniformLocationIndexFromFakeLocation(fake_location); if (location_index >= uniform_locations_.size()) return nullptr; if (!uniform_locations_[location_index].IsActive()) return nullptr; const UniformInfo* info = uniform_locations_[location_index].shader_variable(); size_t element_index = GetArrayElementIndexFromFakeLocation(fake_location); if (static_cast(element_index) >= info->size) return nullptr; *real_location = info->element_locations[element_index]; *array_index = element_index; return info; } bool Program::IsInactiveUniformLocationByFakeLocation( GLint fake_location) const { if (fake_location < 0) return true; size_t location_index = GetUniformLocationIndexFromFakeLocation(fake_location); if (location_index >= uniform_locations_.size()) return false; return uniform_locations_[location_index].IsInactive(); } const std::string* Program::GetAttribMappedName( const std::string& original_name) const { for (auto shader : attached_shaders_) { if (shader) { const std::string* mapped_name = shader->GetAttribMappedName(original_name); if (mapped_name) return mapped_name; } } return nullptr; } const std::string* Program::GetUniformMappedName( const std::string& original_name) const { for (auto shader : attached_shaders_) { if (shader) { const std::string* mapped_name = shader->GetUniformMappedName(original_name); if (mapped_name) return mapped_name; } } return nullptr; } const std::string* Program::GetOriginalNameFromHashedName( const std::string& hashed_name) const { for (auto shader : attached_shaders_) { if (shader) { const std::string* original_name = shader->GetOriginalNameFromHashedName(hashed_name); if (original_name) return original_name; } } return nullptr; } const Program::FragmentInputInfo* Program::GetFragmentInputInfoByFakeLocation( GLint fake_location) const { if (fake_location < 0) return nullptr; size_t location_index = static_cast(fake_location); if (location_index >= fragment_input_locations_.size()) return nullptr; if (!fragment_input_locations_[location_index].IsActive()) return nullptr; return fragment_input_locations_[location_index].shader_variable(); } bool Program::IsInactiveFragmentInputLocationByFakeLocation( GLint fake_location) const { if (fake_location < 0) return true; size_t location_index = static_cast(fake_location); if (location_index >= fragment_input_locations_.size()) return false; return fragment_input_locations_[location_index].IsInactive(); } bool Program::SetUniformLocationBinding( const std::string& name, GLint location) { std::string short_name; int element_index = 0; if (!GetUniformNameSansElement(name, &element_index, &short_name) || element_index != 0) { return false; } bind_uniform_location_map_[short_name] = location; return true; } void Program::SetFragmentInputLocationBinding(const std::string& name, GLint location) { // The client wants to bind either "name" or "name[0]". // GL ES 3.1 spec refers to active array names with language such as: // "if the string identifies the base name of an active array, where the // string would exactly match the name of the variable if the suffix "[0]" // were appended to the string". // At this point we can not know if the string identifies a simple variable, // a base name of an array, or nothing. Store both, so if user overwrites // either, both still work correctly. bind_fragment_input_location_map_[name] = location; bind_fragment_input_location_map_[name + "[0]"] = location; } void Program::SetProgramOutputLocationBinding(const std::string& name, GLuint color_name) { SetProgramOutputLocationIndexedBinding(name, color_name, 0); } void Program::SetProgramOutputLocationIndexedBinding(const std::string& name, GLuint color_name, GLuint index) { bind_program_output_location_index_map_[name] = std::make_pair(color_name, index); bind_program_output_location_index_map_[name + "[0]"] = std::make_pair(color_name, index); } void Program::GetVertexAttribData( const std::string& name, std::string* original_name, GLenum* type) const { DCHECK(original_name); DCHECK(type); Shader* shader = attached_shaders_[ShaderTypeToIndex(GL_VERTEX_SHADER)].get(); if (shader) { // Vertex attributes can not be arrays or structs (GLSL ES 3.00.4, section // 4.3.4, "Input Variables"), so the top level sh::Attribute returns the // information we need. const sh::Attribute* info = shader->GetAttribInfo(name); if (info) { *original_name = info->name; *type = info->type; return; } } // TODO(zmo): this path should never be reached unless there is a serious // bug in the driver or in ANGLE translator. *original_name = name; } const Program::UniformInfo* Program::GetUniformInfo( GLint index) const { if (static_cast(index) >= uniform_infos_.size()) { return NULL; } return &uniform_infos_[index]; } bool Program::SetSamplers( GLint num_texture_units, GLint fake_location, GLsizei count, const GLint* value) { // The caller has checked that the location is active and valid. DCHECK(fake_location >= 0); size_t location_index = GetUniformLocationIndexFromFakeLocation(fake_location); DCHECK(location_index < uniform_locations_.size()); DCHECK(uniform_locations_[location_index].IsActive()); UniformInfo* info = uniform_locations_[location_index].shader_variable(); size_t element_index = GetArrayElementIndexFromFakeLocation(fake_location); if (static_cast(element_index) >= info->size) return true; count = std::min(info->size - static_cast(element_index), count); if (info->IsSampler() && count > 0) { for (GLsizei ii = 0; ii < count; ++ii) { if (value[ii] < 0 || value[ii] >= num_texture_units) { return false; } } std::copy(value, value + count, info->texture_units.begin() + element_index); return true; } return true; } void Program::GetProgramiv(GLenum pname, GLint* params) { switch (pname) { case GL_ACTIVE_ATTRIBUTES: *params = attrib_infos_.size(); break; case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH: // Notice +1 to accomodate NULL terminator. *params = max_attrib_name_length_ + 1; break; case GL_ACTIVE_UNIFORMS: *params = uniform_infos_.size(); break; case GL_ACTIVE_UNIFORM_MAX_LENGTH: // Notice +1 to accomodate NULL terminator. *params = max_uniform_name_length_ + 1; break; case GL_LINK_STATUS: *params = link_status_; break; case GL_INFO_LOG_LENGTH: // Notice +1 to accomodate NULL terminator. *params = log_info_.get() ? (log_info_->size() + 1) : 0; break; case GL_DELETE_STATUS: *params = deleted_; break; case GL_VALIDATE_STATUS: if (!IsValid()) { *params = GL_FALSE; } else { glGetProgramiv(service_id_, pname, params); } break; default: glGetProgramiv(service_id_, pname, params); break; } } bool Program::AttachShader( ShaderManager* shader_manager, Shader* shader) { DCHECK(shader_manager); DCHECK(shader); int index = ShaderTypeToIndex(shader->shader_type()); if (attached_shaders_[index].get() != NULL) { return false; } attached_shaders_[index] = scoped_refptr(shader); shader_manager->UseShader(shader); return true; } bool Program::DetachShader( ShaderManager* shader_manager, Shader* shader) { DCHECK(shader_manager); DCHECK(shader); if (attached_shaders_[ShaderTypeToIndex(shader->shader_type())].get() != shader) { return false; } attached_shaders_[ShaderTypeToIndex(shader->shader_type())] = NULL; shader_manager->UnuseShader(shader); return true; } void Program::DetachShaders(ShaderManager* shader_manager) { DCHECK(shader_manager); for (auto shader : attached_shaders_) { if (shader) { DetachShader(shader_manager, shader.get()); } } } void Program::CompileAttachedShaders() { for (auto shader : attached_shaders_) { if (shader) { shader->DoCompile(); } } } bool Program::AttachedShadersExist() const { for (auto shader : attached_shaders_) { if (!shader) return false; } return true; } bool Program::CanLink() const { for (auto shader : attached_shaders_) { if (!shader || !shader->valid()) { return false; } } return true; } bool Program::DetectShaderVersionMismatch() const { int version = Shader::kUndefinedShaderVersion; for (auto shader : attached_shaders_) { if (shader) { if (version != Shader::kUndefinedShaderVersion && shader->shader_version() != version) { return true; } version = shader->shader_version(); DCHECK(version != Shader::kUndefinedShaderVersion); } } return false; } bool Program::DetectAttribLocationBindingConflicts() const { std::set location_binding_used; for (const auto& key_value : bind_attrib_location_map_) { // Find out if an attribute is statically used in this program's shaders. const sh::Attribute* attrib = NULL; const std::string* mapped_name = GetAttribMappedName(key_value.first); if (!mapped_name) continue; for (auto shader : attached_shaders_) { if (!shader || !shader->valid()) continue; attrib = shader->GetAttribInfo(*mapped_name); if (attrib) { if (attrib->staticUse) break; else attrib = NULL; } } if (attrib) { size_t num_of_locations = 1; switch (attrib->type) { case GL_FLOAT_MAT2: num_of_locations = 2; break; case GL_FLOAT_MAT3: num_of_locations = 3; break; case GL_FLOAT_MAT4: num_of_locations = 4; break; default: break; } for (size_t ii = 0; ii < num_of_locations; ++ii) { GLint loc = key_value.second + ii; auto result = location_binding_used.insert(loc); if (!result.second) return true; } } } return false; } bool Program::DetectUniformLocationBindingConflicts() const { std::set location_binding_used; for (auto it : bind_uniform_location_map_) { // Find out if an attribute is statically used in this program's shaders. const sh::Uniform* uniform = nullptr; const std::string* mapped_name = GetUniformMappedName(it.first); if (!mapped_name) continue; for (auto shader : attached_shaders_) { if (!shader || !shader->valid()) continue; uniform = shader->GetUniformInfo(*mapped_name); if (uniform) { if (uniform->staticUse) break; else uniform = nullptr; } } if (uniform) { auto result = location_binding_used.insert(it.second); if (!result.second) return true; } } return false; } bool Program::DetectUniformsMismatch(std::string* conflicting_name) const { typedef std::map UniformPointerMap; UniformPointerMap uniform_pointer_map; for (auto shader : attached_shaders_) { const UniformMap& shader_uniforms = shader->uniform_map(); for (const auto& key_value : shader_uniforms) { const std::string& name = key_value.first; UniformPointerMap::iterator hit = uniform_pointer_map.find(name); if (hit == uniform_pointer_map.end()) { uniform_pointer_map[name] = &(key_value.second); } else { // If a uniform is in the map, i.e., it has already been declared by // another shader, then the type, precision, etc. must match. if (hit->second->isSameUniformAtLinkTime(key_value.second)) continue; *conflicting_name = name; return true; } } } return false; } bool Program::DetectVaryingsMismatch(std::string* conflicting_name) const { DCHECK(attached_shaders_[0].get() && attached_shaders_[0]->shader_type() == GL_VERTEX_SHADER && attached_shaders_[1].get() && attached_shaders_[1]->shader_type() == GL_FRAGMENT_SHADER); const VaryingMap* vertex_varyings = &(attached_shaders_[0]->varying_map()); const VaryingMap* fragment_varyings = &(attached_shaders_[1]->varying_map()); int shader_version = attached_shaders_[0]->shader_version(); for (const auto& key_value : *fragment_varyings) { const std::string& name = key_value.first; if (IsBuiltInFragmentVarying(name)) continue; VaryingMap::const_iterator hit = vertex_varyings->find(name); if (hit == vertex_varyings->end()) { if (key_value.second.staticUse) { *conflicting_name = name; return true; } continue; } if (!hit->second.isSameVaryingAtLinkTime(key_value.second, shader_version)) { *conflicting_name = name; return true; } } return false; } bool Program::DetectFragmentInputLocationBindingConflicts() const { auto shader = attached_shaders_[ShaderTypeToIndex(GL_FRAGMENT_SHADER)].get(); if (!shader || !shader->valid()) return false; std::set location_binding_used; for (auto it : bind_fragment_input_location_map_) { // Find out if an fragment input is statically used in this program's // shaders. const std::string* mapped_name = shader->GetVaryingMappedName(it.first); if (!mapped_name) continue; const sh::Varying* fragment_input = shader->GetVaryingInfo(*mapped_name); if (fragment_input && fragment_input->staticUse) { auto result = location_binding_used.insert(it.second); if (!result.second) return true; } } return false; } bool Program::DetectProgramOutputLocationBindingConflicts() const { if (feature_info().disable_shader_translator()) { return false; } Shader* shader = attached_shaders_[ShaderTypeToIndex(GL_FRAGMENT_SHADER)].get(); DCHECK(shader && shader->valid()); if (shader->shader_version() == 100) return false; std::set location_binding_used; for (auto const& output_var : shader->output_variable_list()) { if (!output_var.staticUse) continue; size_t count = std::max(output_var.arraySize, 1u); bool is_array = output_var.arraySize > 0; for (size_t jj = 0; jj < count; ++jj) { std::string name = output_var.name; if (is_array) name += std::string("[") + base::IntToString(jj) + "]"; auto it = bind_program_output_location_index_map_.find(name); if (it == bind_program_output_location_index_map_.end()) continue; auto result = location_binding_used.insert(it->second); if (!result.second) return true; } } return false; } bool Program::DetectBuiltInInvariantConflicts() const { DCHECK(attached_shaders_[0].get() && attached_shaders_[0]->shader_type() == GL_VERTEX_SHADER && attached_shaders_[1].get() && attached_shaders_[1]->shader_type() == GL_FRAGMENT_SHADER); const VaryingMap& vertex_varyings = attached_shaders_[0]->varying_map(); const VaryingMap& fragment_varyings = attached_shaders_[1]->varying_map(); bool gl_position_invariant = IsBuiltInInvariant( vertex_varyings, "gl_Position"); bool gl_point_size_invariant = IsBuiltInInvariant( vertex_varyings, "gl_PointSize"); bool gl_frag_coord_invariant = IsBuiltInInvariant( fragment_varyings, "gl_FragCoord"); bool gl_point_coord_invariant = IsBuiltInInvariant( fragment_varyings, "gl_PointCoord"); return ((gl_frag_coord_invariant && !gl_position_invariant) || (gl_point_coord_invariant && !gl_point_size_invariant)); } bool Program::DetectGlobalNameConflicts(std::string* conflicting_name) const { DCHECK(attached_shaders_[0].get() && attached_shaders_[0]->shader_type() == GL_VERTEX_SHADER && attached_shaders_[1].get() && attached_shaders_[1]->shader_type() == GL_FRAGMENT_SHADER); const UniformMap* uniforms[2]; uniforms[0] = &(attached_shaders_[0]->uniform_map()); uniforms[1] = &(attached_shaders_[1]->uniform_map()); const AttributeMap* attribs = &(attached_shaders_[0]->attrib_map()); for (const auto& key_value : *attribs) { for (int ii = 0; ii < 2; ++ii) { if (uniforms[ii]->find(key_value.first) != uniforms[ii]->end()) { *conflicting_name = key_value.first; return true; } } } return false; } bool Program::CheckVaryingsPacking( Program::VaryingsPackingOption option) const { DCHECK(attached_shaders_[0].get() && attached_shaders_[0]->shader_type() == GL_VERTEX_SHADER && attached_shaders_[1].get() && attached_shaders_[1]->shader_type() == GL_FRAGMENT_SHADER); const VaryingMap* vertex_varyings = &(attached_shaders_[0]->varying_map()); const VaryingMap* fragment_varyings = &(attached_shaders_[1]->varying_map()); std::map combined_map; for (const auto& key_value : *fragment_varyings) { if (!key_value.second.staticUse && option == kCountOnlyStaticallyUsed) continue; if (!IsBuiltInFragmentVarying(key_value.first)) { VaryingMap::const_iterator vertex_iter = vertex_varyings->find(key_value.first); if (vertex_iter == vertex_varyings->end() || (!vertex_iter->second.staticUse && option == kCountOnlyStaticallyUsed)) continue; } ShVariableInfo var; var.type = static_cast(key_value.second.type); var.size = std::max(1u, key_value.second.arraySize); combined_map[key_value.first] = var; } if (combined_map.size() == 0) return true; scoped_ptr variables( new ShVariableInfo[combined_map.size()]); size_t index = 0; for (const auto& key_value : combined_map) { variables[index].type = key_value.second.type; variables[index].size = key_value.second.size; ++index; } return ShCheckVariablesWithinPackingLimits( static_cast(manager_->max_varying_vectors()), variables.get(), combined_map.size()); } void Program::GetProgramInfo( ProgramManager* manager, CommonDecoder::Bucket* bucket) const { // NOTE: It seems to me the math in here does not need check for overflow // because the data being calucated from has various small limits. The max // number of attribs + uniforms is somewhere well under 1024. The maximum size // of an identifier is 256 characters. uint32_t num_locations = 0; uint32_t total_string_size = 0; for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) { const VertexAttrib& info = attrib_infos_[ii]; num_locations += 1; total_string_size += info.name.size(); } for (const UniformInfo& info : uniform_infos_) { num_locations += info.element_locations.size(); total_string_size += info.name.size(); } uint32_t num_inputs = attrib_infos_.size() + uniform_infos_.size(); uint32_t input_size = num_inputs * sizeof(ProgramInput); uint32_t location_size = num_locations * sizeof(int32_t); uint32_t size = sizeof(ProgramInfoHeader) + input_size + location_size + total_string_size; bucket->SetSize(size); ProgramInfoHeader* header = bucket->GetDataAs(0, size); ProgramInput* inputs = bucket->GetDataAs( sizeof(ProgramInfoHeader), input_size); int32_t* locations = bucket->GetDataAs( sizeof(ProgramInfoHeader) + input_size, location_size); char* strings = bucket->GetDataAs( sizeof(ProgramInfoHeader) + input_size + location_size, total_string_size); DCHECK(header); DCHECK(inputs); DCHECK(locations); DCHECK(strings); header->link_status = link_status_; header->num_attribs = attrib_infos_.size(); header->num_uniforms = uniform_infos_.size(); for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) { const VertexAttrib& info = attrib_infos_[ii]; inputs->size = info.size; inputs->type = info.type; inputs->location_offset = ComputeOffset(header, locations); inputs->name_offset = ComputeOffset(header, strings); inputs->name_length = info.name.size(); *locations++ = info.location; memcpy(strings, info.name.c_str(), info.name.size()); strings += info.name.size(); ++inputs; } for (const UniformInfo& info : uniform_infos_) { inputs->size = info.size; inputs->type = info.type; inputs->location_offset = ComputeOffset(header, locations); inputs->name_offset = ComputeOffset(header, strings); inputs->name_length = info.name.size(); DCHECK(static_cast(info.size) == info.element_locations.size()); for (size_t jj = 0; jj < info.element_locations.size(); ++jj) { if (info.element_locations[jj] == -1) *locations++ = -1; else *locations++ = ProgramManager::MakeFakeLocation(info.fake_location_base, jj); } memcpy(strings, info.name.c_str(), info.name.size()); strings += info.name.size(); ++inputs; } // NOTE: currently we do not pass inactive uniform binding locations // through the program info call. // NOTE: currently we do not pass fragment input infos through the program // info call, because they are not exposed through any getter function. DCHECK_EQ(ComputeOffset(header, strings), size); } bool Program::GetUniformBlocks(CommonDecoder::Bucket* bucket) const { // The data is packed into the bucket in the following order // 1) header // 2) N entries of block data (except for name and indices) // 3) name1, indices1, name2, indices2, ..., nameN, indicesN // // We query all the data directly through GL calls, assuming they are // cheap through MANGLE. DCHECK(bucket); GLuint program = service_id(); uint32_t header_size = sizeof(UniformBlocksHeader); bucket->SetSize(header_size); // In case we fail. uint32_t num_uniform_blocks = 0; GLint param = GL_FALSE; // We assume program is a valid program service id. glGetProgramiv(program, GL_LINK_STATUS, ¶m); if (param == GL_TRUE) { param = 0; glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, ¶m); num_uniform_blocks = static_cast(param); } if (num_uniform_blocks == 0) { // Although spec allows an implementation to return uniform block info // even if a link fails, for consistency, we disallow that. return true; } std::vector blocks(num_uniform_blocks); base::CheckedNumeric size = sizeof(UniformBlockInfo); size *= num_uniform_blocks; uint32_t entry_size = size.ValueOrDefault(0); size += header_size; std::vector names(num_uniform_blocks); GLint max_name_length = 0; glGetProgramiv( program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &max_name_length); std::vector buffer(max_name_length); GLsizei length; for (uint32_t ii = 0; ii < num_uniform_blocks; ++ii) { param = 0; glGetActiveUniformBlockiv(program, ii, GL_UNIFORM_BLOCK_BINDING, ¶m); blocks[ii].binding = static_cast(param); param = 0; glGetActiveUniformBlockiv(program, ii, GL_UNIFORM_BLOCK_DATA_SIZE, ¶m); blocks[ii].data_size = static_cast(param); blocks[ii].name_offset = size.ValueOrDefault(0); param = 0; glGetActiveUniformBlockiv( program, ii, GL_UNIFORM_BLOCK_NAME_LENGTH, ¶m); DCHECK_GE(max_name_length, param); memset(&buffer[0], 0, param); length = 0; glGetActiveUniformBlockName( program, ii, static_cast(param), &length, &buffer[0]); DCHECK_EQ(param, length + 1); names[ii] = std::string(&buffer[0], length); // TODO(zmo): optimize the name mapping lookup. size_t pos = names[ii].find_first_of('['); const std::string* original_name; std::string array_index_str = ""; if (pos != std::string::npos) { original_name = GetOriginalNameFromHashedName(names[ii].substr(0, pos)); array_index_str = names[ii].substr(pos); } else { original_name = GetOriginalNameFromHashedName(names[ii]); } if (original_name) names[ii] = *original_name + array_index_str; blocks[ii].name_length = names[ii].size() + 1; size += blocks[ii].name_length; param = 0; glGetActiveUniformBlockiv( program, ii, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, ¶m); blocks[ii].active_uniforms = static_cast(param); blocks[ii].active_uniform_offset = size.ValueOrDefault(0); base::CheckedNumeric indices_size = blocks[ii].active_uniforms; indices_size *= sizeof(uint32_t); if (!indices_size.IsValid()) return false; size += indices_size.ValueOrDefault(0); param = 0; glGetActiveUniformBlockiv( program, ii, GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER, ¶m); blocks[ii].referenced_by_vertex_shader = static_cast(param); param = 0; glGetActiveUniformBlockiv( program, ii, GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER, ¶m); blocks[ii].referenced_by_fragment_shader = static_cast(param); } if (!size.IsValid()) return false; uint32_t total_size = size.ValueOrDefault(0); DCHECK_LE(header_size + entry_size, total_size); uint32_t data_size = total_size - header_size - entry_size; bucket->SetSize(total_size); UniformBlocksHeader* header = bucket->GetDataAs(0, header_size); UniformBlockInfo* entries = bucket->GetDataAs( header_size, entry_size); char* data = bucket->GetDataAs(header_size + entry_size, data_size); DCHECK(header); DCHECK(entries); DCHECK(data); // Copy over data for the header and entries. header->num_uniform_blocks = num_uniform_blocks; memcpy(entries, &blocks[0], entry_size); std::vector params; for (uint32_t ii = 0; ii < num_uniform_blocks; ++ii) { // Get active uniform name. memcpy(data, names[ii].c_str(), names[ii].length() + 1); data += names[ii].length() + 1; // Get active uniform indices. if (params.size() < blocks[ii].active_uniforms) params.resize(blocks[ii].active_uniforms); uint32_t num_bytes = blocks[ii].active_uniforms * sizeof(GLint); memset(¶ms[0], 0, num_bytes); glGetActiveUniformBlockiv( program, ii, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, ¶ms[0]); uint32_t* indices = reinterpret_cast(data); for (uint32_t uu = 0; uu < blocks[ii].active_uniforms; ++uu) { indices[uu] = static_cast(params[uu]); } data += num_bytes; } DCHECK_EQ(ComputeOffset(header, data), total_size); return true; } bool Program::GetTransformFeedbackVaryings( CommonDecoder::Bucket* bucket) const { // The data is packed into the bucket in the following order // 1) header // 2) N entries of varying data (except for name) // 3) name1, name2, ..., nameN // // We query all the data directly through GL calls, assuming they are // cheap through MANGLE. DCHECK(bucket); GLuint program = service_id(); uint32_t header_size = sizeof(TransformFeedbackVaryingsHeader); bucket->SetSize(header_size); // In case we fail. GLenum transform_feedback_buffer_mode = 0; GLint param = 0; glGetProgramiv(program, GL_TRANSFORM_FEEDBACK_BUFFER_MODE, ¶m); transform_feedback_buffer_mode = static_cast(param); uint32_t num_transform_feedback_varyings = 0; param = GL_FALSE; // We assume program is a valid program service id. glGetProgramiv(program, GL_LINK_STATUS, ¶m); if (param == GL_TRUE) { param = 0; glGetProgramiv(program, GL_TRANSFORM_FEEDBACK_VARYINGS, ¶m); num_transform_feedback_varyings = static_cast(param); } if (num_transform_feedback_varyings == 0) { TransformFeedbackVaryingsHeader* header = bucket->GetDataAs(0, header_size); header->transform_feedback_buffer_mode = transform_feedback_buffer_mode; return true; } std::vector varyings( num_transform_feedback_varyings); base::CheckedNumeric size = sizeof(TransformFeedbackVaryingInfo); size *= num_transform_feedback_varyings; uint32_t entry_size = size.ValueOrDefault(0); size += header_size; std::vector names(num_transform_feedback_varyings); GLint max_name_length = 0; glGetProgramiv( program, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &max_name_length); if (max_name_length < 1) max_name_length = 1; std::vector buffer(max_name_length); for (uint32_t ii = 0; ii < num_transform_feedback_varyings; ++ii) { GLsizei var_size = 0; GLsizei var_name_length = 0; GLenum var_type = 0; glGetTransformFeedbackVarying( program, ii, max_name_length, &var_name_length, &var_size, &var_type, &buffer[0]); varyings[ii].size = static_cast(var_size); varyings[ii].type = static_cast(var_type); varyings[ii].name_offset = static_cast(size.ValueOrDefault(0)); DCHECK_GT(max_name_length, var_name_length); names[ii] = std::string(&buffer[0], var_name_length); // TODO(zmo): optimize the name mapping lookup. const std::string* original_name = GetOriginalNameFromHashedName(names[ii]); if (original_name) names[ii] = *original_name; varyings[ii].name_length = names[ii].size() + 1; size += names[ii].size(); size += 1; } if (!size.IsValid()) return false; uint32_t total_size = size.ValueOrDefault(0); DCHECK_LE(header_size + entry_size, total_size); uint32_t data_size = total_size - header_size - entry_size; bucket->SetSize(total_size); TransformFeedbackVaryingsHeader* header = bucket->GetDataAs(0, header_size); TransformFeedbackVaryingInfo* entries = bucket->GetDataAs(header_size, entry_size); char* data = bucket->GetDataAs(header_size + entry_size, data_size); DCHECK(header); DCHECK(entries); DCHECK(data); // Copy over data for the header and entries. header->transform_feedback_buffer_mode = transform_feedback_buffer_mode; header->num_transform_feedback_varyings = num_transform_feedback_varyings; memcpy(entries, &varyings[0], entry_size); for (uint32_t ii = 0; ii < num_transform_feedback_varyings; ++ii) { memcpy(data, names[ii].c_str(), names[ii].length() + 1); data += names[ii].length() + 1; } DCHECK_EQ(ComputeOffset(header, data), total_size); return true; } bool Program::GetUniformsES3(CommonDecoder::Bucket* bucket) const { // The data is packed into the bucket in the following order // 1) header // 2) N entries of UniformES3Info // // We query all the data directly through GL calls, assuming they are // cheap through MANGLE. DCHECK(bucket); GLuint program = service_id(); uint32_t header_size = sizeof(UniformsES3Header); bucket->SetSize(header_size); // In case we fail. GLsizei count = 0; GLint param = GL_FALSE; // We assume program is a valid program service id. glGetProgramiv(program, GL_LINK_STATUS, ¶m); if (param == GL_TRUE) { param = 0; glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &count); } if (count == 0) { return true; } base::CheckedNumeric size = sizeof(UniformES3Info); size *= count; uint32_t entry_size = size.ValueOrDefault(0); size += header_size; if (!size.IsValid()) return false; uint32_t total_size = size.ValueOrDefault(0); bucket->SetSize(total_size); UniformsES3Header* header = bucket->GetDataAs(0, header_size); DCHECK(header); header->num_uniforms = static_cast(count); // Instead of GetDataAs, we do GetDataAs. This is // because struct UniformES3Info is defined as five int32_t. // By doing this, we can fill the structs through loops. int32_t* entries = bucket->GetDataAs(header_size, entry_size); DCHECK(entries); const size_t kStride = sizeof(UniformES3Info) / sizeof(int32_t); const GLenum kPname[] = { GL_UNIFORM_BLOCK_INDEX, GL_UNIFORM_OFFSET, GL_UNIFORM_ARRAY_STRIDE, GL_UNIFORM_MATRIX_STRIDE, GL_UNIFORM_IS_ROW_MAJOR, }; const GLint kDefaultValue[] = { -1, -1, -1, -1, 0 }; const size_t kNumPnames = arraysize(kPname); std::vector indices(count); for (GLsizei ii = 0; ii < count; ++ii) { indices[ii] = ii; } std::vector params(count); for (size_t pname_index = 0; pname_index < kNumPnames; ++pname_index) { for (GLsizei ii = 0; ii < count; ++ii) { params[ii] = kDefaultValue[pname_index]; } glGetActiveUniformsiv( program, count, &indices[0], kPname[pname_index], ¶ms[0]); for (GLsizei ii = 0; ii < count; ++ii) { entries[kStride * ii + pname_index] = params[ii]; } } return true; } const Program::ProgramOutputInfo* Program::GetProgramOutputInfo( const std::string& name) const { for (const auto& info : program_output_infos_) { if (info.name == name) { return &info; } } return nullptr; } GLint Program::GetFragDataLocation(const std::string& original_name) const { DCHECK(IsValid()); const ProgramOutputInfo* info = GetProgramOutputInfo(original_name); if (!info) info = GetProgramOutputInfo(original_name + "[0]"); if (!info) return -1; return info->color_name; } GLint Program::GetFragDataIndex(const std::string& original_name) const { DCHECK(IsValid()); const ProgramOutputInfo* info = GetProgramOutputInfo(original_name); if (!info) info = GetProgramOutputInfo(original_name + "[0]"); if (!info) return -1; return info->index; } void Program::TransformFeedbackVaryings(GLsizei count, const char* const* varyings, GLenum buffer_mode) { transform_feedback_varyings_.clear(); for (GLsizei i = 0; i < count; ++i) { transform_feedback_varyings_.push_back(std::string(varyings[i])); } transform_feedback_buffer_mode_ = buffer_mode; } Program::~Program() { if (manager_) { if (manager_->have_context_) { glDeleteProgram(service_id()); } manager_->StopTracking(this); manager_ = NULL; } } ProgramManager::ProgramManager( ProgramCache* program_cache, uint32_t max_varying_vectors, uint32_t max_dual_source_draw_buffers, const GpuPreferences& gpu_preferences, FeatureInfo* feature_info) : program_count_(0), have_context_(true), program_cache_(program_cache), max_varying_vectors_(max_varying_vectors), max_dual_source_draw_buffers_(max_dual_source_draw_buffers), gpu_preferences_(gpu_preferences), feature_info_(feature_info) {} ProgramManager::~ProgramManager() { DCHECK(programs_.empty()); } void ProgramManager::Destroy(bool have_context) { have_context_ = have_context; programs_.clear(); } void ProgramManager::StartTracking(Program* /* program */) { ++program_count_; } void ProgramManager::StopTracking(Program* /* program */) { --program_count_; } Program* ProgramManager::CreateProgram( GLuint client_id, GLuint service_id) { std::pair result = programs_.insert( std::make_pair(client_id, scoped_refptr( new Program(this, service_id)))); DCHECK(result.second); return result.first->second.get(); } Program* ProgramManager::GetProgram(GLuint client_id) { ProgramMap::iterator it = programs_.find(client_id); return it != programs_.end() ? it->second.get() : NULL; } bool ProgramManager::GetClientId(GLuint service_id, GLuint* client_id) const { // This doesn't need to be fast. It's only used during slow queries. for (const auto& key_value : programs_) { if (key_value.second->service_id() == service_id) { *client_id = key_value.first; return true; } } return false; } ProgramCache* ProgramManager::program_cache() const { return program_cache_; } bool ProgramManager::IsOwned(Program* program) const { for (const auto& key_value : programs_) { if (key_value.second.get() == program) { return true; } } return false; } void ProgramManager::RemoveProgramInfoIfUnused( ShaderManager* shader_manager, Program* program) { DCHECK(shader_manager); DCHECK(program); DCHECK(IsOwned(program)); if (program->IsDeleted() && !program->InUse()) { program->DetachShaders(shader_manager); for (ProgramMap::iterator it = programs_.begin(); it != programs_.end(); ++it) { if (it->second.get() == program) { programs_.erase(it); return; } } NOTREACHED(); } } void ProgramManager::MarkAsDeleted( ShaderManager* shader_manager, Program* program) { DCHECK(shader_manager); DCHECK(program); DCHECK(IsOwned(program)); program->MarkAsDeleted(); RemoveProgramInfoIfUnused(shader_manager, program); } void ProgramManager::UseProgram(Program* program) { DCHECK(program); DCHECK(IsOwned(program)); program->IncUseCount(); } void ProgramManager::UnuseProgram( ShaderManager* shader_manager, Program* program) { DCHECK(shader_manager); DCHECK(program); DCHECK(IsOwned(program)); program->DecUseCount(); RemoveProgramInfoIfUnused(shader_manager, program); } void ProgramManager::ClearUniforms(Program* program) { DCHECK(program); program->ClearUniforms(&zero_); } int32_t ProgramManager::MakeFakeLocation(int32_t index, int32_t element) { return index + element * 0x10000; } } // namespace gles2 } // namespace gpu