| // 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/program_manager.h" | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <limits> | |
| #include <memory> | |
| #include <set> | |
| #include <string_view> | |
| #include <utility> | |
| #include <vector> | |
| #include "base/check.h" | |
| #include "base/command_line.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/heap_array.h" | |
| #include "base/containers/span.h" | |
| #include "base/containers/span_writer.h" | |
| #include "base/logging.h" | |
| #include "base/numerics/safe_math.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/strings/string_view_util.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_format.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_utils.h" | |
| #include "gpu/command_buffer/service/decoder_context.h" | |
| #include "gpu/command_buffer/service/feature_info.h" | |
| #include "gpu/command_buffer/service/program_cache.h" | |
| #include "gpu/command_buffer/service/shader_manager.h" | |
| #include "gpu/config/gpu_preferences.h" | |
| #include "third_party/re2/src/re2/re2.h" | |
| #include "ui/gl/gl_version_info.h" | |
| #include "ui/gl/progress_reporter.h" | |
| 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(); | |
| } | |
| } | |
| // 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.back() != ']') { | |
| *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<GLint> 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. | |
| auto kBuiltInVaryings = std::to_array<const char*>({ | |
| "gl_FragCoord", | |
| "gl_FrontFacing", | |
| "gl_PointCoord", | |
| }); | |
| for (size_t ii = 0; ii < std::size(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; | |
| } | |
| // This is used for vertex shader input variables and fragment shader output | |
| // variables. | |
| ShaderVariableBaseType InputOutputTypeToBaseType(bool is_input, GLenum type) { | |
| switch (type) { | |
| case GL_INT: | |
| case GL_INT_VEC2: | |
| case GL_INT_VEC3: | |
| case GL_INT_VEC4: | |
| return SHADER_VARIABLE_INT; | |
| case GL_UNSIGNED_INT: | |
| case GL_UNSIGNED_INT_VEC2: | |
| case GL_UNSIGNED_INT_VEC3: | |
| case GL_UNSIGNED_INT_VEC4: | |
| return SHADER_VARIABLE_UINT; | |
| case GL_FLOAT: | |
| case GL_FLOAT_VEC2: | |
| case GL_FLOAT_VEC3: | |
| case GL_FLOAT_VEC4: | |
| return SHADER_VARIABLE_FLOAT; | |
| case GL_FLOAT_MAT2: | |
| case GL_FLOAT_MAT3: | |
| case GL_FLOAT_MAT4: | |
| case GL_FLOAT_MAT2x3: | |
| case GL_FLOAT_MAT3x2: | |
| case GL_FLOAT_MAT2x4: | |
| case GL_FLOAT_MAT4x2: | |
| case GL_FLOAT_MAT3x4: | |
| case GL_FLOAT_MAT4x3: | |
| DCHECK(is_input); | |
| return SHADER_VARIABLE_FLOAT; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| GLsizeiptr VertexShaderOutputBaseTypeToSize(GLenum type) { | |
| switch (type) { | |
| case GL_INT: | |
| return 4; | |
| case GL_INT_VEC2: | |
| return 8; | |
| case GL_INT_VEC3: | |
| return 12; | |
| case GL_INT_VEC4: | |
| return 16; | |
| case GL_UNSIGNED_INT: | |
| return 4; | |
| case GL_UNSIGNED_INT_VEC2: | |
| return 8; | |
| case GL_UNSIGNED_INT_VEC3: | |
| return 12; | |
| case GL_UNSIGNED_INT_VEC4: | |
| return 16; | |
| case GL_FLOAT: | |
| return 4; | |
| case GL_FLOAT_VEC2: | |
| return 8; | |
| case GL_FLOAT_VEC3: | |
| return 12; | |
| case GL_FLOAT_VEC4: | |
| return 16; | |
| case GL_FLOAT_MAT2: | |
| return 16; | |
| case GL_FLOAT_MAT3: | |
| return 36; | |
| case GL_FLOAT_MAT4: | |
| return 64; | |
| case GL_FLOAT_MAT2x3: | |
| return 24; | |
| case GL_FLOAT_MAT3x2: | |
| return 24; | |
| case GL_FLOAT_MAT2x4: | |
| return 32; | |
| case GL_FLOAT_MAT4x2: | |
| return 32; | |
| case GL_FLOAT_MAT3x4: | |
| return 48; | |
| case GL_FLOAT_MAT4x3: | |
| return 48; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| GLsizeiptr VertexShaderOutputTypeToSize(const sh::Varying& varying) { | |
| base::CheckedNumeric<GLsizeiptr> total = 0; | |
| if (varying.fields.size()) { // struct case. | |
| for (auto const& field : varying.fields) { | |
| // struct field for vertex shader outputs can only be basic types. | |
| GLsizeiptr size = VertexShaderOutputBaseTypeToSize(field.type); | |
| DCHECK(size); | |
| total += size; | |
| } | |
| } else { | |
| GLsizeiptr size = VertexShaderOutputBaseTypeToSize(varying.type); | |
| DCHECK(size); | |
| total = size; | |
| if (varying.isArray()) { // array case. | |
| total *= varying.getOutermostArraySize(); | |
| } | |
| } | |
| return total.ValueOrDefault(std::numeric_limits<GLsizeiptr>::max()); | |
| } | |
| size_t LocationCountForAttribType(GLenum type) { | |
| switch (type) { | |
| case GL_FLOAT_MAT2: | |
| case GL_FLOAT_MAT2x3: | |
| case GL_FLOAT_MAT2x4: | |
| return 2; | |
| case GL_FLOAT_MAT3x2: | |
| case GL_FLOAT_MAT3: | |
| case GL_FLOAT_MAT3x4: | |
| return 3; | |
| case GL_FLOAT_MAT4x2: | |
| case GL_FLOAT_MAT4x3: | |
| case GL_FLOAT_MAT4: | |
| return 4; | |
| default: | |
| return 1; | |
| } | |
| } | |
| } // anonymous namespace. | |
| Program::UniformInfo::UniformInfo() | |
| : size(0), | |
| type(GL_NONE), | |
| accepts_api_type(UniformApiType::kUniformNone), | |
| 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<GLint>& service_locations) | |
| : size(service_locations.size()), | |
| type(_type), | |
| accepts_api_type(UniformApiType::kUniformNone), | |
| 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 = UniformApiType::kUniform1i; | |
| break; | |
| case GL_INT_VEC2: | |
| accepts_api_type = UniformApiType::kUniform2i; | |
| break; | |
| case GL_INT_VEC3: | |
| accepts_api_type = UniformApiType::kUniform3i; | |
| break; | |
| case GL_INT_VEC4: | |
| accepts_api_type = UniformApiType::kUniform4i; | |
| break; | |
| case GL_UNSIGNED_INT: | |
| accepts_api_type = UniformApiType::kUniform1ui; | |
| break; | |
| case GL_UNSIGNED_INT_VEC2: | |
| accepts_api_type = UniformApiType::kUniform2ui; | |
| break; | |
| case GL_UNSIGNED_INT_VEC3: | |
| accepts_api_type = UniformApiType::kUniform3ui; | |
| break; | |
| case GL_UNSIGNED_INT_VEC4: | |
| accepts_api_type = UniformApiType::kUniform4ui; | |
| break; | |
| case GL_BOOL: | |
| accepts_api_type = UniformApiType::kUniform1i | | |
| UniformApiType::kUniform1ui | | |
| UniformApiType::kUniform1f; | |
| break; | |
| case GL_BOOL_VEC2: | |
| accepts_api_type = UniformApiType::kUniform2i | | |
| UniformApiType::kUniform2ui | | |
| UniformApiType::kUniform2f; | |
| break; | |
| case GL_BOOL_VEC3: | |
| accepts_api_type = UniformApiType::kUniform3i | | |
| UniformApiType::kUniform3ui | | |
| UniformApiType::kUniform3f; | |
| break; | |
| case GL_BOOL_VEC4: | |
| accepts_api_type = UniformApiType::kUniform4i | | |
| UniformApiType::kUniform4ui | | |
| UniformApiType::kUniform4f; | |
| break; | |
| case GL_FLOAT: | |
| accepts_api_type = UniformApiType::kUniform1f; | |
| break; | |
| case GL_FLOAT_VEC2: | |
| accepts_api_type = UniformApiType::kUniform2f; | |
| break; | |
| case GL_FLOAT_VEC3: | |
| accepts_api_type = UniformApiType::kUniform3f; | |
| break; | |
| case GL_FLOAT_VEC4: | |
| accepts_api_type = UniformApiType::kUniform4f; | |
| break; | |
| case GL_FLOAT_MAT2: | |
| accepts_api_type = UniformApiType::kUniformMatrix2f; | |
| break; | |
| case GL_FLOAT_MAT3: | |
| accepts_api_type = UniformApiType::kUniformMatrix3f; | |
| break; | |
| case GL_FLOAT_MAT4: | |
| accepts_api_type = UniformApiType::kUniformMatrix4f; | |
| break; | |
| case GL_FLOAT_MAT2x3: | |
| accepts_api_type = UniformApiType::kUniformMatrix2x3f; | |
| break; | |
| case GL_FLOAT_MAT2x4: | |
| accepts_api_type = UniformApiType::kUniformMatrix2x4f; | |
| break; | |
| case GL_FLOAT_MAT3x2: | |
| accepts_api_type = UniformApiType::kUniformMatrix3x2f; | |
| break; | |
| case GL_FLOAT_MAT3x4: | |
| accepts_api_type = UniformApiType::kUniformMatrix3x4f; | |
| break; | |
| case GL_FLOAT_MAT4x2: | |
| accepts_api_type = UniformApiType::kUniformMatrix4x2f; | |
| break; | |
| case GL_FLOAT_MAT4x3: | |
| accepts_api_type = UniformApiType::kUniformMatrix4x3f; | |
| break; | |
| case GL_SAMPLER_2D: | |
| case GL_SAMPLER_2D_RECT_ANGLE: | |
| 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: | |
| case GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT: | |
| accepts_api_type = UniformApiType::kUniform1i; | |
| break; | |
| default: | |
| NOTREACHED() << "Unhandled UniformInfo type " << type; | |
| } | |
| DCHECK_LT(0, size); | |
| DCHECK(is_array || size == 1); | |
| size_t num_texture_units = IsSampler() ? static_cast<size_t>(size) : 0u; | |
| texture_units.clear(); | |
| texture_units.resize(num_texture_units, 0); | |
| } | |
| Program::UniformInfo::UniformInfo(const UniformInfo& other) = default; | |
| Program::UniformInfo::~UniformInfo() = default; | |
| 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), | |
| active_transform_feedback_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), | |
| draw_id_uniform_location_(-1), | |
| base_vertex_uniform_location_(-1), | |
| base_instance_uniform_location_(-1), | |
| transform_feedback_buffer_mode_(GL_NONE), | |
| effective_transform_feedback_buffer_mode_(GL_NONE), | |
| fragment_output_type_mask_(0u), | |
| fragment_output_written_mask_(0u) { | |
| DCHECK(manager_); | |
| manager_->StartTracking(this); | |
| uint32_t packed_size = (manager_->max_vertex_attribs() + 15) / 16; | |
| vertex_input_base_type_mask_.resize(packed_size); | |
| vertex_input_active_mask_.resize(packed_size); | |
| ClearVertexInputMasks(); | |
| } | |
| void Program::Reset() { | |
| valid_ = false; | |
| link_status_ = false; | |
| max_uniform_name_length_ = 0; | |
| max_attrib_name_length_ = 0; | |
| attrib_infos_.clear(); | |
| uniform_locations_.clear(); | |
| uniform_infos_.clear(); | |
| program_output_infos_.clear(); | |
| sampler_indices_.clear(); | |
| attrib_location_to_index_map_.clear(); | |
| fragment_output_type_mask_ = 0u; | |
| fragment_output_written_mask_ = 0u; | |
| draw_id_uniform_location_ = -1; | |
| base_vertex_uniform_location_ = -1; | |
| base_instance_uniform_location_ = -1; | |
| ClearVertexInputMasks(); | |
| } | |
| void Program::ClearVertexInputMasks() { | |
| for (uint32_t ii = 0; ii < vertex_input_base_type_mask_.size(); ++ii) { | |
| vertex_input_base_type_mask_[ii] = 0u; | |
| vertex_input_active_mask_[ii] = 0u; | |
| } | |
| } | |
| void Program::UpdateFragmentOutputBaseTypes() { | |
| fragment_output_type_mask_ = 0u; | |
| fragment_output_written_mask_ = 0u; | |
| Shader* fragment_shader = | |
| shaders_from_last_successful_link_[ShaderTypeToIndex(GL_FRAGMENT_SHADER)] | |
| .get(); | |
| DCHECK(fragment_shader); | |
| for (auto const& output : fragment_shader->output_variable_list()) { | |
| int location = output.location; | |
| DCHECK(location == -1 || | |
| (location >= 0 && | |
| location < static_cast<int>(manager_->max_draw_buffers()))); | |
| if (location == -1) | |
| location = 0; | |
| if (ProgramManager::HasBuiltInPrefix(output.name)) { | |
| if (output.name != "gl_FragColor" && output.name != "gl_FragData") | |
| continue; | |
| } | |
| int count = | |
| static_cast<int>(output.isArray() ? output.getOutermostArraySize() : 1); | |
| // TODO(zmo): Handle the special case in ES2 where gl_FragColor could | |
| // be broadcasting to all draw buffers. | |
| DCHECK_LE(location + count, | |
| static_cast<int>(manager_->max_draw_buffers())); | |
| for (int ii = location; ii < location + count; ++ii) { | |
| // TODO(zmo): This does not work with glBindFragDataLocationIndexed. | |
| // crbug.com/628010 | |
| // For example: | |
| // glBindFragDataLocationIndexed(program, loc, 0, "FragData0"); | |
| // glBindFragDataLocationIndexed(program, loc, 1, "FragData1"); | |
| // The program links OK, but both calling glGetFragDataLocation on both | |
| // "FragData0" and "FragData1" returns 0. | |
| int shift_bits = ii * 2; | |
| fragment_output_written_mask_ |= 0x3 << shift_bits; | |
| fragment_output_type_mask_ |= | |
| InputOutputTypeToBaseType(false, output.type) << shift_bits; | |
| } | |
| } | |
| } | |
| void Program::UpdateVertexInputBaseTypes() { | |
| ClearVertexInputMasks(); | |
| for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) { | |
| const VertexAttrib& input = attrib_infos_[ii]; | |
| if (ProgramManager::HasBuiltInPrefix(input.name)) { | |
| continue; | |
| } | |
| DCHECK_LE(input.location + input.location_count, | |
| manager_->max_vertex_attribs()); | |
| for (size_t location = input.location; | |
| location < input.location + input.location_count; ++location) { | |
| int shift_bits = (location % 16) * 2; | |
| vertex_input_active_mask_[location / 16] |= 0x3 << shift_bits; | |
| vertex_input_base_type_mask_[location / 16] |= | |
| InputOutputTypeToBaseType(true, input.type) << shift_bits; | |
| } | |
| } | |
| } | |
| void Program::UpdateUniformBlockSizeInfo() { | |
| if (feature_info().IsWebGL1OrES2Context()) { | |
| // Uniform blocks do not exist in ES2. | |
| return; | |
| } | |
| uniform_block_size_info_.clear(); | |
| GLint num_uniform_blocks = 0; | |
| glGetProgramiv(service_id_, GL_ACTIVE_UNIFORM_BLOCKS, &num_uniform_blocks); | |
| uniform_block_size_info_.resize(num_uniform_blocks); | |
| for (GLint ii = 0; ii < num_uniform_blocks; ++ii) { | |
| GLint binding = 0; | |
| glGetActiveUniformBlockiv( | |
| service_id_, ii, GL_UNIFORM_BLOCK_BINDING, &binding); | |
| uniform_block_size_info_[ii].binding = static_cast<GLuint>(binding); | |
| GLint size = 0; | |
| glGetActiveUniformBlockiv( | |
| service_id_, ii, GL_UNIFORM_BLOCK_DATA_SIZE, &size); | |
| uniform_block_size_info_[ii].data_size = static_cast<GLuint>(size); | |
| } | |
| } | |
| void Program::DecrementActiveTransformFeedbackCount() { | |
| CHECK_GT(active_transform_feedback_count_, 0); | |
| --active_transform_feedback_count_; | |
| } | |
| void Program::SetUniformBlockBinding(GLuint index, GLuint binding) { | |
| DCHECK_GT(uniform_block_size_info_.size(), index); | |
| uniform_block_size_info_[index].binding = binding; | |
| } | |
| void Program::UpdateTransformFeedbackInfo() { | |
| effective_transform_feedback_buffer_mode_ = transform_feedback_buffer_mode_; | |
| effective_transform_feedback_varyings_ = transform_feedback_varyings_; | |
| Shader* vertex_shader = shaders_from_last_successful_link_[0].get(); | |
| DCHECK(vertex_shader); | |
| if (effective_transform_feedback_buffer_mode_ == GL_INTERLEAVED_ATTRIBS) { | |
| transform_feedback_data_size_per_vertex_.resize(1); | |
| } else { | |
| transform_feedback_data_size_per_vertex_.resize( | |
| effective_transform_feedback_varyings_.size()); | |
| } | |
| base::CheckedNumeric<GLsizeiptr> total = 0; | |
| for (size_t ii = 0; ii < effective_transform_feedback_varyings_.size(); | |
| ++ii) { | |
| const std::string& client_name = effective_transform_feedback_varyings_[ii]; | |
| const std::string* service_name = | |
| vertex_shader->GetVaryingMappedName(client_name); | |
| DCHECK(service_name); | |
| const sh::Varying* varying = vertex_shader->GetVaryingInfo(*service_name); | |
| DCHECK(varying); | |
| GLsizeiptr size = VertexShaderOutputTypeToSize(*varying); | |
| DCHECK(size); | |
| if (effective_transform_feedback_buffer_mode_ == GL_INTERLEAVED_ATTRIBS) { | |
| total += size; | |
| } else { | |
| transform_feedback_data_size_per_vertex_[ii] = size; | |
| } | |
| } | |
| if (effective_transform_feedback_buffer_mode_ == GL_INTERLEAVED_ATTRIBS) { | |
| transform_feedback_data_size_per_vertex_[0] = | |
| total.ValueOrDefault(std::numeric_limits<GLsizeiptr>::max()); | |
| } | |
| } | |
| void Program::UpdateDrawIDUniformLocation() { | |
| DCHECK(IsValid()); | |
| GLint fake_location = GetUniformFakeLocation("gl_DrawID"); | |
| draw_id_uniform_location_ = -1; | |
| GLint array_index; | |
| GetUniformInfoByFakeLocation(fake_location, &draw_id_uniform_location_, | |
| &array_index); | |
| } | |
| void Program::UpdateBaseVertexUniformLocation() { | |
| DCHECK(IsValid()); | |
| GLint fake_location = GetUniformFakeLocation("gl_BaseVertex"); | |
| base_vertex_uniform_location_ = -1; | |
| GLint array_index; | |
| GetUniformInfoByFakeLocation(fake_location, &base_vertex_uniform_location_, | |
| &array_index); | |
| } | |
| void Program::UpdateBaseInstanceUniformLocation() { | |
| DCHECK(IsValid()); | |
| GLint fake_location = GetUniformFakeLocation("gl_BaseInstance"); | |
| base_instance_uniform_location_ = -1; | |
| GLint array_index; | |
| GetUniformInfoByFakeLocation(fake_location, &base_instance_uniform_location_, | |
| &array_index); | |
| } | |
| std::string Program::ProcessLogInfo(const std::string& log) { | |
| std::string output; | |
| std::string_view 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; | |
| } | |
| output.append(input); | |
| return output; | |
| } | |
| void Program::UpdateLogInfo() { | |
| GLint max_len = 0; | |
| glGetProgramiv(service_id_, GL_INFO_LOG_LENGTH, &max_len); | |
| if (max_len == 0) { | |
| set_log_info(nullptr); | |
| return; | |
| } | |
| base::HeapArray<char> temp = base::HeapArray<char>::Uninit(max_len); | |
| GLint len = 0; | |
| glGetProgramInfoLog(service_id_, max_len, &len, temp.data()); | |
| DCHECK(max_len == 0 || len < max_len); | |
| DCHECK(len == 0 || temp[len] == '\0'); | |
| std::string log(temp.data(), len); | |
| log = ProcessLogInfo(log); | |
| set_log_info(log.empty() ? nullptr : log.c_str()); | |
| } | |
| void Program::Update() { | |
| Reset(); | |
| UpdateLogInfo(); | |
| link_status_ = true; | |
| uniforms_cleared_ = false; | |
| GLint num_attribs = 0; | |
| GLint max_len = 0; | |
| size_t num_locations = 0; | |
| 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? | |
| base::HeapArray<char> name_buffer = base::HeapArray<char>::Uninit(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.data()); | |
| DCHECK(max_len == 0 || length < max_len); | |
| DCHECK(length == 0 || name_buffer[length] == '\0'); | |
| std::string original_name; | |
| GetVertexAttribData(std::string(name_buffer.data(), length), &original_name, | |
| &type); | |
| base::CheckedNumeric<size_t> location_count = size; | |
| location_count *= LocationCountForAttribType(type); | |
| size_t safe_location_count = 0; | |
| if (!location_count.AssignIfValid(&safe_location_count)) | |
| return; | |
| GLint location; | |
| if (base::StartsWith(base::as_string_view(name_buffer), "gl_", | |
| base::CompareCase::SENSITIVE)) { | |
| // Built-in attributes, for example, gl_VertexID, are still considered | |
| // as active but their location is -1. | |
| // However, on MacOSX, drivers return 0 in this case. | |
| // Set |location| to -1 directly. | |
| location = -1; | |
| } else { | |
| // TODO(gman): Should we check for error? | |
| location = glGetAttribLocation(service_id_, name_buffer.data()); | |
| base::CheckedNumeric<size_t> max_location = location; | |
| max_location += safe_location_count; | |
| size_t safe_max_location = 0; | |
| if (!max_location.AssignIfValid(&safe_max_location)) | |
| return; | |
| num_locations = std::max(num_locations, safe_max_location); | |
| } | |
| attrib_infos_.push_back( | |
| VertexAttrib(1, type, original_name, location, safe_location_count)); | |
| max_attrib_name_length_ = std::max( | |
| max_attrib_name_length_, static_cast<GLsizei>(original_name.size())); | |
| } | |
| // Create attrib location to index map. | |
| attrib_location_to_index_map_.resize(num_locations, -1); | |
| for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) { | |
| const VertexAttrib& info = attrib_infos_[ii]; | |
| if (info.location < 0) | |
| continue; | |
| DCHECK_LE(info.location + info.location_count, num_locations); | |
| for (size_t j = 0; j < info.location_count; ++j) | |
| attrib_location_to_index_map_[info.location + j] = ii; | |
| } | |
| 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; | |
| } | |
| } | |
| if (!UpdateUniforms()) | |
| return; | |
| 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; | |
| } | |
| } | |
| UpdateProgramOutputs(); | |
| UpdateFragmentOutputBaseTypes(); | |
| UpdateVertexInputBaseTypes(); | |
| UpdateUniformBlockSizeInfo(); | |
| UpdateTransformFeedbackInfo(); | |
| valid_ = true; | |
| } | |
| bool 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<size_t>(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 true; | |
| 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); | |
| base::HeapArray<char> name_buffer = | |
| base::HeapArray<char>::Uninit(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.data()); | |
| // Avoid immediately crashing if glGetActiveUniform misbehaves. | |
| if (!size) | |
| return false; | |
| DCHECK(name_length < name_buffer_length); | |
| DCHECK(name_length == 0 || name_buffer[name_length] == '\0'); | |
| std::string service_name(name_buffer.data(), 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 = shaders_from_last_successful_link_[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->isArray(); | |
| type = info->type; | |
| size = std::max(1u, info->getOutermostArraySize()); | |
| } 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->isArray(); | |
| type = info->type; | |
| size = std::max(1u, info->getOutermostArraySize()); | |
| 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<GLint> 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::NumberToString(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<GLsizei>(info.name.size())); | |
| } | |
| return true; | |
| } | |
| void Program::UpdateProgramOutputs() { | |
| if (!feature_info().gl_version_info().IsAtLeastGLES(3, 0) || | |
| feature_info().disable_shader_translator()) { | |
| return; | |
| } | |
| Shader* fragment_shader = | |
| shaders_from_last_successful_link_[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.isArray()) { | |
| 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.getOutermostArraySize(); ++ii) { | |
| std::string array_spec(std::string("[") + base::NumberToString(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()) { | |
| // This is called before program linking, so refer to attached_shaders_. | |
| Shader* vertex_shader = attached_shaders_[0].get(); | |
| if (!vertex_shader) { | |
| set_log_info("TransformFeedbackVaryings: missing vertex shader"); | |
| return false; | |
| } | |
| std::vector<const char*> 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; | |
| } | |
| // This is called before program linking, so refer to attached_shaders_. | |
| 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.getOutermostArraySize(), 1u); | |
| bool is_array = output_var.isArray(); | |
| 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::NumberToString(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()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| bool Program::Link(ShaderManager* manager, | |
| DecoderClient* client) { | |
| ClearLinkStatus(); | |
| if (!AttachedShadersExist()) { | |
| set_log_info("missing shaders"); | |
| return false; | |
| } | |
| bool link = true; | |
| ProgramCache* cache = manager_->program_cache_; | |
| // This is called before program linking, so refer to attached_shaders_. | |
| if (cache && | |
| !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; | |
| 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_, client); | |
| link = success != ProgramCache::PROGRAM_LOAD_SUCCESS; | |
| } | |
| } | |
| 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 (DetectInterfaceBlocksMismatch(&conflicting_name)) { | |
| std::string info_log = | |
| "Interface blocks with the same name but different" | |
| " fields/layout: " + | |
| conflicting_name; | |
| set_log_info(ProcessLogInfo(info_log).c_str()); | |
| 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 (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()) { | |
| set_log_info("Varyings over maximum register limit"); | |
| return false; | |
| } | |
| ExecuteBindAttribLocationCalls(); | |
| if (!ExecuteTransformFeedbackVaryingsCall()) { | |
| return false; | |
| } | |
| ExecuteProgramOutputBindCalls(); | |
| if (cache && feature_info().gl_version_info().IsAtLeastGLES(3, 0)) { | |
| glProgramParameteri(service_id(), GL_PROGRAM_BINARY_RETRIEVABLE_HINT, | |
| GL_TRUE); | |
| } | |
| glLinkProgram(service_id()); | |
| } | |
| GLint success = 0; | |
| glGetProgramiv(service_id(), GL_LINK_STATUS, &success); | |
| if (success == GL_TRUE) { | |
| for (size_t ii = 0; ii < kMaxAttachedShaders; ++ii) | |
| shaders_from_last_successful_link_[ii] = attached_shaders_[ii]; | |
| Update(); | |
| if (link) { | |
| // Here it makes no difference because attached_shaders_ and | |
| // shaders_from_last_successful_link_ are still the same. | |
| // 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_, | |
| effective_transform_feedback_varyings_, | |
| effective_transform_feedback_buffer_mode_, client); | |
| } | |
| } | |
| } 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<int>(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<GLsizei>(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 { | |
| // This is called by DetectAttribLocationBindingConflicts() and | |
| // ExecuteBindAttribLocationCalls(). Both are called before program linking, | |
| // so refer to attached_shaders_. | |
| 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 { | |
| // This is called by DetectUniformLocationBindingConflicts(), which is called | |
| // before program linking, so refer to attached_shaders_. | |
| 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 { | |
| // This is called by ProcessLogInfo(), which could in turn be called by | |
| // UpdateLogInfo(). All these cases are either before program linking, or | |
| // right after program linking and shader attachments haven't been changed, | |
| // so refer to attached_shaders_. | |
| // TODO(zmo): The only exception is for Validate(), for which | |
| // shaders_from_last_successful_link_ should be used. This is minor issue | |
| // though, leading to log information from ValidateProgram() could end up | |
| // with hashed variable names. | |
| 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 sh::Varying* Program::GetVaryingInfo( | |
| const std::string& hashed_name) const { | |
| for (auto shader : shaders_from_last_successful_link_) { | |
| if (shader) { | |
| const sh::Varying* info = shader->GetVaryingInfo(hashed_name); | |
| if (info) | |
| return info; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| const sh::InterfaceBlock* Program::GetInterfaceBlockInfo( | |
| const std::string& hashed_name) const { | |
| for (auto shader : shaders_from_last_successful_link_) { | |
| if (shader) { | |
| const sh::InterfaceBlock* info = | |
| shader->GetInterfaceBlockInfo(hashed_name); | |
| if (info) | |
| return info; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| 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::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 = | |
| shaders_from_last_successful_link_[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<size_t>(index) >= uniform_infos_.size()) { | |
| return nullptr; | |
| } | |
| 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<GLsizei>(element_index) >= info->size) | |
| return true; | |
| count = std::min(info->size - static_cast<GLsizei>(element_index), count); | |
| if (info->IsSampler() && count > 0) { | |
| for (GLsizei ii = 0; ii < count; ++ii) { | |
| if (UNSAFE_TODO(value[ii]) < 0 || | |
| UNSAFE_TODO(value[ii]) >= num_texture_units) { | |
| return false; | |
| } | |
| } | |
| std::copy(value, UNSAFE_TODO(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() != nullptr) { | |
| return false; | |
| } | |
| attached_shaders_[index] = scoped_refptr<Shader>(shader); | |
| shader_manager->UseShader(shader); | |
| return true; | |
| } | |
| bool Program::IsShaderAttached(Shader* shader) { | |
| return attached_shaders_[ShaderTypeToIndex(shader->shader_type())].get() == | |
| shader; | |
| } | |
| void Program::DetachShader( | |
| ShaderManager* shader_manager, | |
| Shader* shader) { | |
| DCHECK(shader_manager); | |
| DCHECK(shader); | |
| DCHECK(IsShaderAttached(shader)); | |
| attached_shaders_[ShaderTypeToIndex(shader->shader_type())] = nullptr; | |
| shader_manager->UnuseShader(shader); | |
| } | |
| 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; | |
| // This is called before program linking, so refer to attached_shaders_. | |
| 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<GLint> 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 = nullptr; | |
| 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 (shader->shader_version() >= 300 || attrib->staticUse) | |
| break; | |
| else | |
| attrib = nullptr; | |
| } | |
| } | |
| if (attrib) { | |
| size_t num_of_locations = LocationCountForAttribType(attrib->type); | |
| 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<GLint> location_binding_used; | |
| for (const 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<std::string, const sh::Uniform*> 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::DetectInterfaceBlocksMismatch( | |
| std::string* conflicting_name) const { | |
| std::map<std::string, const sh::InterfaceBlock*> interface_pointer_map; | |
| for (auto shader : attached_shaders_) { | |
| const InterfaceBlockMap& shader_interfaces = shader->interface_block_map(); | |
| for (const auto& it : shader_interfaces) { | |
| const auto& name = it.first; | |
| auto hit = interface_pointer_map.find(name); | |
| if (hit == interface_pointer_map.end()) { | |
| interface_pointer_map[name] = &(it.second); | |
| } else { | |
| // If an interface is in the map, i.e., it has already been declared by | |
| // another shader, then the layout must match. | |
| if (hit->second->isSameInterfaceBlockAtLinkTime(it.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::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<LocationIndexMap::mapped_type> location_binding_used; | |
| for (auto const& output_var : shader->output_variable_list()) { | |
| if (!output_var.staticUse) | |
| continue; | |
| size_t count = std::max(output_var.getOutermostArraySize(), 1u); | |
| bool is_array = output_var.isArray(); | |
| for (size_t jj = 0; jj < count; ++jj) { | |
| std::string name = output_var.name; | |
| if (is_array) | |
| name += std::string("[") + base::NumberToString(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); | |
| std::array<const UniformMap*, 2> uniforms; | |
| 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() 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<std::string, const sh::ShaderVariable*> combined_map; | |
| for (const auto& key_value : *fragment_varyings) { | |
| if (!key_value.second.staticUse) { | |
| 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) { | |
| continue; | |
| } | |
| } | |
| combined_map[key_value.first] = &key_value.second; | |
| } | |
| if (combined_map.size() == 0) | |
| return true; | |
| std::vector<sh::ShaderVariable> variables; | |
| for (const auto& key_value : combined_map) { | |
| variables.push_back(*key_value.second); | |
| } | |
| return sh::CheckVariablesWithinPackingLimits( | |
| static_cast<int>(manager_->max_varying_vectors()), variables); | |
| } | |
| 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); | |
| const uint32_t inputs_offset = sizeof(ProgramInfoHeader); | |
| const uint32_t locations_offset = inputs_offset + input_size; | |
| const uint32_t strings_offset = locations_offset + location_size; | |
| ProgramInfoHeader& header = | |
| bucket->GetDataAsSpan<ProgramInfoHeader>(0, 1u)[0]; | |
| base::span<ProgramInput> inputs = | |
| bucket->GetDataAsSpan<ProgramInput>(inputs_offset, num_inputs); | |
| base::span<int32_t> locations = | |
| bucket->GetDataAsSpan<int32_t>(locations_offset, num_locations); | |
| base::span<uint8_t> strings = | |
| bucket->GetDataAsByteSpan(strings_offset, total_string_size); | |
| header.link_status = link_status_; | |
| header.num_attribs = attrib_infos_.size(); | |
| header.num_uniforms = uniform_infos_.size(); | |
| size_t input_index = 0; | |
| size_t location_index = 0; | |
| base::SpanWriter<uint8_t> string_writer(strings); | |
| for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) { | |
| const VertexAttrib& info = attrib_infos_[ii]; | |
| ProgramInput& input = inputs[input_index++]; | |
| input.size = info.size; | |
| input.type = info.type; | |
| input.location_offset = locations_offset + location_index * sizeof(int32_t); | |
| input.name_offset = strings_offset + string_writer.num_written(); | |
| input.name_length = info.name.size(); | |
| locations[location_index++] = info.location; | |
| CHECK(string_writer.Write(base::as_byte_span(info.name))); | |
| } | |
| for (const UniformInfo& info : uniform_infos_) { | |
| ProgramInput& input = inputs[input_index++]; | |
| input.size = info.size; | |
| input.type = info.type; | |
| input.location_offset = locations_offset + location_index * sizeof(int32_t); | |
| input.name_offset = strings_offset + string_writer.num_written(); | |
| input.name_length = info.name.size(); | |
| DCHECK(static_cast<size_t>(info.size) == info.element_locations.size()); | |
| for (size_t jj = 0; jj < info.element_locations.size(); ++jj) { | |
| if (info.element_locations[jj] == -1) | |
| locations[location_index++] = -1; | |
| else | |
| locations[location_index++] = | |
| ProgramManager::MakeFakeLocation(info.fake_location_base, jj); | |
| } | |
| CHECK(string_writer.Write(base::as_byte_span(info.name))); | |
| } | |
| // 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(strings_offset + string_writer.num_written(), 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<uint32_t>(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<UniformBlockInfo> blocks(num_uniform_blocks); | |
| base::CheckedNumeric<uint32_t> size = sizeof(UniformBlockInfo); | |
| size *= num_uniform_blocks; | |
| uint32_t entry_size = size.ValueOrDefault(0); | |
| size += header_size; | |
| std::vector<std::string> names(num_uniform_blocks); | |
| GLint max_name_length = 0; | |
| glGetProgramiv( | |
| program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &max_name_length); | |
| std::vector<GLchar> 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<uint32_t>(param); | |
| param = 0; | |
| glGetActiveUniformBlockiv(program, ii, GL_UNIFORM_BLOCK_DATA_SIZE, ¶m); | |
| blocks[ii].data_size = static_cast<uint32_t>(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); | |
| UNSAFE_TODO(memset(&buffer[0], 0, param)); | |
| length = 0; | |
| glGetActiveUniformBlockName( | |
| program, ii, static_cast<GLsizei>(param), &length, &buffer[0]); | |
| DCHECK_EQ(param, length + 1); | |
| names[ii] = std::string(&buffer[0], length); | |
| size_t pos = names[ii].find_first_of('['); | |
| const sh::InterfaceBlock* interface_block = nullptr; | |
| std::string array_index_str = ""; | |
| if (pos != std::string::npos) { | |
| interface_block = GetInterfaceBlockInfo(names[ii].substr(0, pos)); | |
| array_index_str = names[ii].substr(pos); | |
| } else { | |
| interface_block = GetInterfaceBlockInfo(names[ii]); | |
| } | |
| if (interface_block) | |
| names[ii] = interface_block->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<uint32_t>(param); | |
| blocks[ii].active_uniform_offset = size.ValueOrDefault(0); | |
| base::CheckedNumeric<uint32_t> 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<uint32_t>(param); | |
| param = 0; | |
| glGetActiveUniformBlockiv( | |
| program, ii, GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER, ¶m); | |
| blocks[ii].referenced_by_fragment_shader = static_cast<uint32_t>(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->GetDataAsSpan<UniformBlocksHeader>(0, 1u)[0]; | |
| base::span<UniformBlockInfo> entries = | |
| bucket->GetDataAsSpan<UniformBlockInfo>(header_size, num_uniform_blocks); | |
| base::span<uint8_t> data = | |
| bucket->GetDataAsByteSpan(header_size + entry_size, data_size); | |
| // Copy over data for the header and entries. | |
| header.num_uniform_blocks = num_uniform_blocks; | |
| entries.copy_from(base::span(blocks)); | |
| base::SpanWriter<uint8_t> data_writer(data); | |
| std::vector<GLint> params; | |
| for (uint32_t ii = 0; ii < num_uniform_blocks; ++ii) { | |
| // Get active uniform name. | |
| CHECK(data_writer.Write(base::as_byte_span(names[ii]))); | |
| CHECK(data_writer.Write(uint8_t{0})); | |
| // Get active uniform indices. | |
| if (params.size() < blocks[ii].active_uniforms) | |
| params.resize(blocks[ii].active_uniforms); | |
| std::ranges::fill(base::span(params).first(blocks[ii].active_uniforms), 0); | |
| glGetActiveUniformBlockiv( | |
| program, ii, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, ¶ms[0]); | |
| for (uint32_t uu = 0; uu < blocks[ii].active_uniforms; ++uu) { | |
| CHECK( | |
| data_writer.WriteU32NativeEndian(static_cast<uint32_t>(params[uu]))); | |
| } | |
| } | |
| DCHECK_EQ(header_size + entry_size + data_writer.num_written(), 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<GLenum>(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<uint32_t>(param); | |
| } | |
| if (num_transform_feedback_varyings == 0) { | |
| TransformFeedbackVaryingsHeader& header = | |
| bucket->GetDataAsSpan<TransformFeedbackVaryingsHeader>(0, 1u)[0]; | |
| header.transform_feedback_buffer_mode = transform_feedback_buffer_mode; | |
| return true; | |
| } | |
| std::vector<TransformFeedbackVaryingInfo> varyings( | |
| num_transform_feedback_varyings); | |
| base::CheckedNumeric<uint32_t> size = sizeof(TransformFeedbackVaryingInfo); | |
| size *= num_transform_feedback_varyings; | |
| uint32_t entry_size = size.ValueOrDefault(0); | |
| size += header_size; | |
| std::vector<std::string> 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<char> 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<uint32_t>(var_size); | |
| varyings[ii].type = static_cast<uint32_t>(var_type); | |
| varyings[ii].name_offset = static_cast<uint32_t>(size.ValueOrDefault(0)); | |
| DCHECK_GT(max_name_length, var_name_length); | |
| names[ii] = std::string(&buffer[0], var_name_length); | |
| const sh::Varying* varying = GetVaryingInfo(names[ii]); | |
| if (varying) | |
| names[ii] = varying->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->GetDataAsSpan<TransformFeedbackVaryingsHeader>(0, 1u)[0]; | |
| base::span<TransformFeedbackVaryingInfo> entries = | |
| bucket->GetDataAsSpan<TransformFeedbackVaryingInfo>( | |
| header_size, num_transform_feedback_varyings); | |
| base::span<uint8_t> data = | |
| bucket->GetDataAsByteSpan(header_size + entry_size, data_size); | |
| // 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; | |
| entries.copy_from(base::span(varyings)); | |
| base::SpanWriter<uint8_t> data_writer(data); | |
| for (uint32_t ii = 0; ii < num_transform_feedback_varyings; ++ii) { | |
| CHECK(data_writer.Write(base::as_byte_span(names[ii]))); | |
| CHECK(data_writer.Write(uint8_t{0})); | |
| } | |
| DCHECK_EQ(header_size + entry_size + data_writer.num_written(), 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<uint32_t> size = sizeof(UniformES3Info); | |
| size *= count; | |
| size += header_size; | |
| if (!size.IsValid()) | |
| return false; | |
| uint32_t total_size = size.ValueOrDefault(0); | |
| bucket->SetSize(total_size); | |
| UniformsES3Header& header = | |
| bucket->GetDataAsSpan<UniformsES3Header>(0, 1u)[0]; | |
| header.num_uniforms = static_cast<uint32_t>(count); | |
| // Instead of GetDataAsSpan<UniformES3Info>, we do GetDataAsSpan<int32_t>. | |
| // This is because struct UniformES3Info is defined as five int32_t. | |
| // By doing this, we can fill the structs through loops. | |
| const size_t kStride = sizeof(UniformES3Info) / sizeof(int32_t); | |
| base::span<int32_t> entries = | |
| bucket->GetDataAsSpan<int32_t>(header_size, kStride * count); | |
| DCHECK_EQ(entries.size(), kStride * static_cast<size_t>(count)); | |
| const auto kPname = std::to_array<GLenum>({ | |
| GL_UNIFORM_BLOCK_INDEX, | |
| GL_UNIFORM_OFFSET, | |
| GL_UNIFORM_ARRAY_STRIDE, | |
| GL_UNIFORM_MATRIX_STRIDE, | |
| GL_UNIFORM_IS_ROW_MAJOR, | |
| }); | |
| const auto kDefaultValue = std::to_array<GLint>({-1, -1, -1, -1, 0}); | |
| const size_t kNumPnames = std::size(kPname); | |
| std::vector<GLuint> indices(count); | |
| for (GLsizei ii = 0; ii < count; ++ii) { | |
| indices[ii] = ii; | |
| } | |
| std::vector<GLint> 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(UNSAFE_TODO(varyings[i]))); | |
| } | |
| transform_feedback_buffer_mode_ = buffer_mode; | |
| } | |
| Program::~Program() { | |
| if (manager_) { | |
| if (manager_->have_context_) { | |
| glDeleteProgram(service_id()); | |
| } | |
| manager_->StopTracking(this); | |
| manager_ = nullptr; | |
| } | |
| } | |
| ProgramManager::ProgramManager(ProgramCache* program_cache, | |
| uint32_t max_varying_vectors, | |
| uint32_t max_draw_buffers, | |
| uint32_t max_dual_source_draw_buffers, | |
| uint32_t max_vertex_attribs, | |
| const GpuPreferences& gpu_preferences, | |
| FeatureInfo* feature_info, | |
| gl::ProgressReporter* progress_reporter) | |
| : program_count_(0), | |
| have_context_(true), | |
| program_cache_(program_cache), | |
| max_varying_vectors_(max_varying_vectors), | |
| max_draw_buffers_(max_draw_buffers), | |
| max_dual_source_draw_buffers_(max_dual_source_draw_buffers), | |
| max_vertex_attribs_(max_vertex_attribs), | |
| gpu_preferences_(gpu_preferences), | |
| feature_info_(feature_info), | |
| progress_reporter_(progress_reporter) {} | |
| ProgramManager::~ProgramManager() { | |
| DCHECK(programs_.empty()); | |
| } | |
| void ProgramManager::Destroy(bool have_context) { | |
| have_context_ = have_context; | |
| while (!programs_.empty()) { | |
| programs_.erase(programs_.begin()); | |
| if (progress_reporter_) | |
| progress_reporter_->ReportProgress(); | |
| } | |
| } | |
| 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<ProgramMap::iterator, bool> result = | |
| programs_.insert(std::make_pair( | |
| client_id, base::MakeRefCounted<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() : nullptr; | |
| } | |
| 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; | |
| } | |
| bool ProgramManager::HasCachedCompileStatus(Shader* shader) const { | |
| if (program_cache_) { | |
| return program_cache_->HasSuccessfullyCompiledShader( | |
| shader->last_compiled_signature()); | |
| } | |
| 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::UpdateDrawIDUniformLocation(Program* program) { | |
| DCHECK(program); | |
| program->UpdateDrawIDUniformLocation(); | |
| } | |
| void ProgramManager::UpdateBaseVertexUniformLocation(Program* program) { | |
| DCHECK(program); | |
| program->UpdateBaseVertexUniformLocation(); | |
| } | |
| void ProgramManager::UpdateBaseInstanceUniformLocation(Program* program) { | |
| DCHECK(program); | |
| program->UpdateBaseInstanceUniformLocation(); | |
| } | |
| int32_t ProgramManager::MakeFakeLocation(int32_t index, int32_t element) { | |
| return index + element * 0x10000; | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |