| // Copyright 2013 The Flutter 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 "impeller/renderer/backend/gles/buffer_bindings_gles.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <sstream> |
| #include <vector> |
| |
| #include "impeller/base/config.h" |
| #include "impeller/base/validation.h" |
| #include "impeller/renderer/backend/gles/device_buffer_gles.h" |
| #include "impeller/renderer/backend/gles/formats_gles.h" |
| #include "impeller/renderer/backend/gles/sampler_gles.h" |
| #include "impeller/renderer/backend/gles/texture_gles.h" |
| |
| namespace impeller { |
| |
| BufferBindingsGLES::BufferBindingsGLES() = default; |
| |
| BufferBindingsGLES::~BufferBindingsGLES() = default; |
| |
| bool BufferBindingsGLES::RegisterVertexStageInput( |
| const ProcTableGLES& gl, |
| const std::vector<ShaderStageIOSlot>& p_inputs) { |
| // Attrib locations have to be iterated over in order of location because we |
| // will be calculating offsets later. |
| auto inputs = p_inputs; |
| std::sort(inputs.begin(), inputs.end(), [](const auto& lhs, const auto& rhs) { |
| return lhs.location < rhs.location; |
| }); |
| |
| std::vector<VertexAttribPointer> vertex_attrib_arrays; |
| size_t offset = 0u; |
| for (const auto& input : inputs) { |
| VertexAttribPointer attrib; |
| attrib.index = input.location; |
| // Component counts must be 1, 2, 3 or 4. Do that validation now. |
| if (input.vec_size < 1u || input.vec_size > 4u) { |
| return false; |
| } |
| attrib.size = input.vec_size; |
| auto type = ToVertexAttribType(input.type); |
| if (!type.has_value()) { |
| return false; |
| } |
| attrib.type = type.value(); |
| attrib.normalized = GL_FALSE; |
| attrib.offset = offset; |
| offset += (input.bit_width * input.vec_size) / 8; |
| vertex_attrib_arrays.emplace_back(attrib); |
| } |
| for (auto& array : vertex_attrib_arrays) { |
| array.stride = offset; |
| } |
| vertex_attrib_arrays_ = std::move(vertex_attrib_arrays); |
| return true; |
| } |
| |
| static std::string NormalizeUniformKey(const std::string& key) { |
| std::stringstream stream; |
| for (size_t i = 0, count = key.length(); i < count; i++) { |
| auto ch = key.data()[i]; |
| if (ch == '_') { |
| continue; |
| } |
| stream << static_cast<char>(toupper(ch)); |
| } |
| return stream.str(); |
| } |
| |
| static std::string CreateUnifiormMemberKey(const std::string& struct_name, |
| const std::string& member, |
| bool is_array) { |
| std::stringstream stream; |
| stream << struct_name << "." << member; |
| if (is_array) { |
| stream << "[0]"; |
| } |
| return NormalizeUniformKey(stream.str()); |
| } |
| |
| static std::string CreateUnifiormMemberKey( |
| const std::string& non_struct_member) { |
| return NormalizeUniformKey(non_struct_member); |
| } |
| |
| bool BufferBindingsGLES::ReadUniformsBindings(const ProcTableGLES& gl, |
| GLuint program) { |
| if (!gl.IsProgram(program)) { |
| return false; |
| } |
| GLint max_name_size = 0; |
| gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_size); |
| |
| GLint uniform_count = 0; |
| gl.GetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count); |
| for (GLint i = 0; i < uniform_count; i++) { |
| std::vector<GLchar> name; |
| name.resize(max_name_size); |
| GLsizei written_count = 0u; |
| GLint uniform_var_size = 0u; |
| GLenum uniform_type = GL_FLOAT; |
| // Note: Active uniforms are defined as uniforms that may have an impact on |
| // the output of the shader. Drivers are allowed to (and often do) |
| // optimize out unused uniforms. |
| gl.GetActiveUniform(program, // program |
| i, // index |
| max_name_size, // buffer_size |
| &written_count, // length |
| &uniform_var_size, // size |
| &uniform_type, // type |
| name.data() // name |
| ); |
| auto location = gl.GetUniformLocation(program, name.data()); |
| if (location == -1) { |
| VALIDATION_LOG << "Could not query the location of an active uniform."; |
| return false; |
| } |
| if (written_count <= 0) { |
| VALIDATION_LOG << "Uniform name could not be read for active uniform."; |
| return false; |
| } |
| uniform_locations_[NormalizeUniformKey(std::string{ |
| name.data(), static_cast<size_t>(written_count)})] = location; |
| } |
| return true; |
| } |
| |
| bool BufferBindingsGLES::BindVertexAttributes(const ProcTableGLES& gl, |
| size_t vertex_offset) const { |
| for (const auto& array : vertex_attrib_arrays_) { |
| gl.EnableVertexAttribArray(array.index); |
| gl.VertexAttribPointer(array.index, // index |
| array.size, // size (must be 1, 2, 3, or 4) |
| array.type, // type |
| array.normalized, // normalized |
| array.stride, // stride |
| reinterpret_cast<const GLvoid*>(static_cast<GLsizei>( |
| vertex_offset + array.offset)) // pointer |
| ); |
| } |
| |
| return true; |
| } |
| |
| bool BufferBindingsGLES::BindUniformData( |
| const ProcTableGLES& gl, |
| Allocator& transients_allocator, |
| const Bindings& vertex_bindings, |
| const Bindings& fragment_bindings) const { |
| for (const auto& buffer : vertex_bindings.buffers) { |
| if (!BindUniformBuffer(gl, transients_allocator, buffer.second)) { |
| return false; |
| } |
| } |
| for (const auto& buffer : fragment_bindings.buffers) { |
| if (!BindUniformBuffer(gl, transients_allocator, buffer.second)) { |
| return false; |
| } |
| } |
| |
| if (!BindTextures(gl, vertex_bindings, ShaderStage::kVertex)) { |
| return false; |
| } |
| |
| if (!BindTextures(gl, fragment_bindings, ShaderStage::kFragment)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool BufferBindingsGLES::UnbindVertexAttributes(const ProcTableGLES& gl) const { |
| for (const auto& array : vertex_attrib_arrays_) { |
| gl.DisableVertexAttribArray(array.index); |
| } |
| return true; |
| } |
| |
| bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, |
| Allocator& transients_allocator, |
| const BufferResource& buffer) const { |
| const auto* metadata = buffer.isa; |
| if (metadata == nullptr) { |
| // Vertex buffer bindings don't have metadata as those definitions are |
| // already handled by vertex attrib pointers. Keep going. |
| return true; |
| } |
| |
| auto device_buffer = |
| buffer.resource.buffer->GetDeviceBuffer(transients_allocator); |
| if (!device_buffer) { |
| VALIDATION_LOG << "Device buffer not found."; |
| return false; |
| } |
| const auto& device_buffer_gles = DeviceBufferGLES::Cast(*device_buffer); |
| const uint8_t* buffer_ptr = |
| device_buffer_gles.GetBufferData() + buffer.resource.range.offset; |
| |
| if (metadata->members.empty()) { |
| VALIDATION_LOG << "Uniform buffer had no members. This is currently " |
| "unsupported in the OpenGL ES backend. Use a uniform " |
| "buffer block."; |
| return false; |
| } |
| |
| for (const auto& member : metadata->members) { |
| if (member.type == ShaderType::kVoid) { |
| // Void types are used for padding. We are obviously not going to find |
| // mappings for these. Keep going. |
| continue; |
| } |
| |
| size_t element_count = member.array_elements.value_or(1); |
| |
| const auto member_key = |
| CreateUnifiormMemberKey(metadata->name, member.name, element_count > 1); |
| const auto location = uniform_locations_.find(member_key); |
| if (location == uniform_locations_.end()) { |
| // The list of uniform locations only contains "active" uniforms that are |
| // not optimized out. So this situation is expected to happen when unused |
| // uniforms are present in the shader. |
| continue; |
| } |
| |
| size_t element_stride = member.byte_length / element_count; |
| |
| auto* buffer_data = |
| reinterpret_cast<const GLfloat*>(buffer_ptr + member.offset); |
| |
| std::vector<uint8_t> array_element_buffer; |
| if (element_count > 1) { |
| // When binding uniform arrays, the elements must be contiguous. Copy the |
| // uniforms to a temp buffer to eliminate any padding needed by the other |
| // backends. |
| array_element_buffer.resize(member.size * element_count); |
| for (size_t element_i = 0; element_i < element_count; element_i++) { |
| std::memcpy(array_element_buffer.data() + element_i * member.size, |
| reinterpret_cast<const char*>(buffer_data) + |
| element_i * element_stride, |
| member.size); |
| } |
| buffer_data = |
| reinterpret_cast<const GLfloat*>(array_element_buffer.data()); |
| } |
| |
| switch (member.type) { |
| case ShaderType::kFloat: |
| switch (member.size) { |
| case sizeof(Matrix): |
| gl.UniformMatrix4fv(location->second, // location |
| element_count, // count |
| GL_FALSE, // normalize |
| buffer_data // data |
| ); |
| continue; |
| case sizeof(Vector4): |
| gl.Uniform4fv(location->second, // location |
| element_count, // count |
| buffer_data // data |
| ); |
| continue; |
| case sizeof(Vector3): |
| gl.Uniform3fv(location->second, // location |
| element_count, // count |
| buffer_data // data |
| ); |
| continue; |
| case sizeof(Vector2): |
| gl.Uniform2fv(location->second, // location |
| element_count, // count |
| buffer_data // data |
| ); |
| continue; |
| case sizeof(Scalar): |
| gl.Uniform1fv(location->second, // location |
| element_count, // count |
| buffer_data // data |
| ); |
| continue; |
| } |
| case ShaderType::kBoolean: |
| case ShaderType::kSignedByte: |
| case ShaderType::kUnsignedByte: |
| case ShaderType::kSignedShort: |
| case ShaderType::kUnsignedShort: |
| case ShaderType::kSignedInt: |
| case ShaderType::kUnsignedInt: |
| case ShaderType::kSignedInt64: |
| case ShaderType::kUnsignedInt64: |
| case ShaderType::kAtomicCounter: |
| case ShaderType::kUnknown: |
| case ShaderType::kVoid: |
| case ShaderType::kHalfFloat: |
| case ShaderType::kDouble: |
| case ShaderType::kStruct: |
| case ShaderType::kImage: |
| case ShaderType::kSampledImage: |
| case ShaderType::kSampler: |
| VALIDATION_LOG << "Could not bind uniform buffer data for key: " |
| << member_key; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool BufferBindingsGLES::BindTextures(const ProcTableGLES& gl, |
| const Bindings& bindings, |
| ShaderStage stage) const { |
| size_t active_index = 0; |
| for (const auto& texture : bindings.textures) { |
| const auto& texture_gles = TextureGLES::Cast(*texture.second.resource); |
| if (texture.second.isa == nullptr) { |
| VALIDATION_LOG << "No metadata found for texture binding."; |
| return false; |
| } |
| |
| const auto uniform_key = CreateUnifiormMemberKey(texture.second.isa->name); |
| auto uniform = uniform_locations_.find(uniform_key); |
| if (uniform == uniform_locations_.end()) { |
| VALIDATION_LOG << "Could not find uniform for key: " << uniform_key; |
| return false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| /// Set the active texture unit. |
| /// |
| if (active_index >= gl.GetCapabilities()->GetMaxTextureUnits(stage)) { |
| VALIDATION_LOG << "Texture units specified exceed the capabilities for " |
| "this shader stage."; |
| return false; |
| } |
| gl.ActiveTexture(GL_TEXTURE0 + active_index); |
| |
| //-------------------------------------------------------------------------- |
| /// Bind the texture. |
| /// |
| if (!texture_gles.Bind()) { |
| return false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| /// If there is a sampler for the texture at the same index, configure the |
| /// bound texture using that sampler. |
| /// |
| auto sampler = bindings.samplers.find(texture.first); |
| if (sampler != bindings.samplers.end()) { |
| const auto& sampler_gles = SamplerGLES::Cast(*sampler->second.resource); |
| if (!sampler_gles.ConfigureBoundTexture(texture_gles, gl)) { |
| return false; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| /// Set the texture uniform location. |
| /// |
| gl.Uniform1i(uniform->second, active_index); |
| |
| //-------------------------------------------------------------------------- |
| /// Bump up the active index at binding. |
| /// |
| active_index++; |
| } |
| return true; |
| } |
| |
| } // namespace impeller |