blob: 570615c2245867b635921b7767e4dd4014048dbd [file] [log] [blame]
// 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 <cstring>
#include <vector>
#include "impeller/base/validation.h"
#include "impeller/core/buffer_view.h"
#include "impeller/core/device_buffer.h"
#include "impeller/core/shader_types.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"
#include "impeller/renderer/command.h"
namespace impeller {
// This prefix is used in the names of inputs generated by ANGLE's framebuffer
// fetch emulation.
static constexpr std::string_view kAngleInputAttachmentPrefix =
"ANGLEInputAttachment";
BufferBindingsGLES::BufferBindingsGLES() = default;
BufferBindingsGLES::~BufferBindingsGLES() = default;
bool BufferBindingsGLES::RegisterVertexStageInput(
const ProcTableGLES& gl,
const std::vector<ShaderStageIOSlot>& p_inputs,
const std::vector<ShaderStageBufferLayout>& layouts) {
std::vector<std::vector<VertexAttribPointer>> vertex_attrib_arrays(
layouts.size());
// Every layout corresponds to a vertex binding.
// As we record, separate the attributes into buckets for each layout in
// ascending order. We do this because later on, we'll need to associate each
// of the attributes with bound buffers corresponding to the binding.
for (auto layout_i = 0u; layout_i < layouts.size(); layout_i++) {
const auto& layout = layouts[layout_i];
for (const auto& input : p_inputs) {
if (input.binding != layout_i) {
continue;
}
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 = input.offset;
attrib.stride = layout.stride;
vertex_attrib_arrays[layout_i].push_back(attrib);
}
}
vertex_attrib_arrays_ = std::move(vertex_attrib_arrays);
return true;
}
static std::string NormalizeUniformKey(const std::string& key) {
std::string result;
result.reserve(key.length());
for (char ch : key) {
if (ch != '_') {
result.push_back(toupper(ch));
}
}
return result;
}
static std::string CreateUniformMemberKey(const std::string& struct_name,
const std::string& member,
bool is_array) {
std::string result;
result.reserve(struct_name.length() + member.length() + (is_array ? 4 : 1));
result += struct_name;
if (!member.empty()) {
result += '.';
result += member;
}
if (is_array) {
result += "[0]";
}
return NormalizeUniformKey(result);
}
static std::string CreateUniformMemberKey(
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;
}
program_handle_ = program;
if (gl.GetDescription()->GetGlVersion().IsAtLeast(Version{3, 0, 0})) {
return ReadUniformsBindingsV3(gl, program);
}
return ReadUniformsBindingsV2(gl, program);
}
bool BufferBindingsGLES::ReadUniformsBindingsV3(const ProcTableGLES& gl,
GLuint program) {
program_handle_ = program;
GLint uniform_blocks = 0;
gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &uniform_blocks);
for (GLint i = 0; i < uniform_blocks; i++) {
GLint name_length = 0;
gl.GetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_NAME_LENGTH,
&name_length);
std::vector<GLchar> name;
name.resize(name_length);
GLint length = 0;
gl.GetActiveUniformBlockName(program, i, name_length, &length, name.data());
GLuint block_index = gl.GetUniformBlockIndex(program, name.data());
gl.UniformBlockBinding(program_handle_, block_index, i);
ubo_locations_[std::string{name.data(), static_cast<size_t>(length)}] =
std::make_pair(block_index, i);
}
use_ubo_ = true;
return ReadUniformsBindingsV2(gl, program);
}
bool BufferBindingsGLES::ReadUniformsBindingsV2(const ProcTableGLES& gl,
GLuint program) {
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);
// Query the Program for all active uniform locations, and
// record this via normalized key.
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
);
// Skip unrecognized variables generated by ANGLE.
if (gl.GetCapabilities()->IsANGLE()) {
if (written_count >=
static_cast<GLsizei>(kAngleInputAttachmentPrefix.length()) &&
std::string_view(name.data(), kAngleInputAttachmentPrefix.length()) ==
kAngleInputAttachmentPrefix) {
continue;
}
}
auto location = gl.GetUniformLocation(program, name.data());
if (location == -1) {
if (use_ubo_) {
continue;
}
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 binding,
size_t vertex_offset) {
if (binding >= vertex_attrib_arrays_.size()) {
return false;
}
if (!gl.GetCapabilities()->IsES()) {
FML_DCHECK(vertex_array_object_ == 0);
gl.GenVertexArrays(1, &vertex_array_object_);
gl.BindVertexArray(vertex_array_object_);
}
for (const auto& array : vertex_attrib_arrays_[binding]) {
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,
const std::vector<TextureAndSampler>& bound_textures,
const std::vector<BufferResource>& bound_buffers,
Range texture_range,
Range buffer_range) {
for (auto i = 0u; i < buffer_range.length; i++) {
if (!BindUniformBuffer(gl, bound_buffers[buffer_range.offset + i])) {
return false;
}
}
std::optional<size_t> next_unit_index =
BindTextures(gl, bound_textures, texture_range, ShaderStage::kVertex);
if (!next_unit_index.has_value()) {
return false;
}
if (!BindTextures(gl, bound_textures, texture_range, ShaderStage::kFragment,
*next_unit_index)
.has_value()) {
return false;
}
return true;
}
bool BufferBindingsGLES::UnbindVertexAttributes(const ProcTableGLES& gl) {
for (const auto& array : vertex_attrib_arrays_) {
for (const auto& attribute : array) {
gl.DisableVertexAttribArray(attribute.index);
}
}
if (!gl.GetCapabilities()->IsES()) {
gl.DeleteVertexArrays(1, &vertex_array_object_);
vertex_array_object_ = 0;
}
return true;
}
GLint BufferBindingsGLES::ComputeTextureLocation(
const ShaderMetadata* metadata) {
auto location = binding_map_.find(metadata->name);
if (location != binding_map_.end()) {
return location->second[0];
}
auto& locations = binding_map_[metadata->name] = {};
auto computed_location =
uniform_locations_.find(CreateUniformMemberKey(metadata->name));
if (computed_location == uniform_locations_.end()) {
locations.push_back(-1);
} else {
locations.push_back(computed_location->second);
}
return locations[0];
}
const std::vector<GLint>& BufferBindingsGLES::ComputeUniformLocations(
const ShaderMetadata* metadata) {
BindingMap::iterator location = binding_map_.find(metadata->name);
if (location != binding_map_.end()) {
return location->second;
}
// For each metadata member, look up the binding location and record
// it in the binding map.
std::vector<GLint>& locations = binding_map_[metadata->name] = {};
locations.reserve(metadata->members.size());
for (const ShaderStructMemberMetadata& 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.
locations.push_back(-1);
continue;
}
size_t element_count = member.array_elements.value_or(1);
const std::string member_key =
CreateUniformMemberKey(metadata->name, member.name, element_count > 1);
const absl::flat_hash_map<std::string, GLint>::iterator computed_location =
uniform_locations_.find(member_key);
if (computed_location == uniform_locations_.end()) {
// Uniform was not active.
locations.push_back(-1);
continue;
}
locations.push_back(computed_location->second);
}
return locations;
}
bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl,
const BufferResource& buffer) {
const ShaderMetadata* metadata = buffer.GetMetadata();
const DeviceBuffer* device_buffer = buffer.resource.GetBuffer();
if (!device_buffer) {
VALIDATION_LOG << "Device buffer not found.";
return false;
}
const DeviceBufferGLES& device_buffer_gles =
DeviceBufferGLES::Cast(*device_buffer);
if (use_ubo_) {
return BindUniformBufferV3(gl, buffer.resource, metadata,
device_buffer_gles);
}
return BindUniformBufferV2(gl, buffer.resource, metadata, device_buffer_gles);
}
bool BufferBindingsGLES::BindUniformBufferV3(
const ProcTableGLES& gl,
const BufferView& buffer,
const ShaderMetadata* metadata,
const DeviceBufferGLES& device_buffer_gles) {
absl::flat_hash_map<std::string, std::pair<GLint, GLuint>>::iterator it =
ubo_locations_.find(metadata->name);
if (it == ubo_locations_.end()) {
return BindUniformBufferV2(gl, buffer, metadata, device_buffer_gles);
}
const auto& [block_index, binding_point] = it->second;
if (!device_buffer_gles.BindAndUploadDataIfNecessary(
DeviceBufferGLES::BindingType::kUniformBuffer)) {
return false;
}
auto handle = device_buffer_gles.GetHandle();
if (!handle.has_value()) {
return false;
}
gl.BindBufferRange(GL_UNIFORM_BUFFER, binding_point, handle.value(),
buffer.GetRange().offset, buffer.GetRange().length);
return true;
}
bool BufferBindingsGLES::BindUniformBufferV2(
const ProcTableGLES& gl,
const BufferView& buffer,
const ShaderMetadata* metadata,
const DeviceBufferGLES& device_buffer_gles) {
const uint8_t* buffer_ptr =
device_buffer_gles.GetBufferData() + buffer.GetRange().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;
}
const std::vector<GLint>& locations = ComputeUniformLocations(metadata);
for (size_t i = 0u; i < metadata->members.size(); i++) {
const ShaderStructMemberMetadata& member = metadata->members[i];
GLint location = locations[i];
// Void type or inactive uniform.
if (location == -1 || member.type == ShaderType::kVoid) {
continue;
}
size_t element_count = member.array_elements.value_or(1);
size_t element_stride = member.byte_length / element_count;
auto* buffer_data =
reinterpret_cast<const GLfloat*>(buffer_ptr + member.offset);
// 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 if the array elements have padding.
std::vector<uint8_t> array_element_buffer;
if (element_count > 1 && element_stride != member.size) {
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());
}
if (member.type != ShaderType::kFloat) {
VALIDATION_LOG << "Could not bind uniform buffer data for key: "
<< member.name << " : " << static_cast<int>(member.type);
return false;
}
switch (member.size) {
case sizeof(Matrix):
gl.UniformMatrix4fv(location, // location
element_count, // count
GL_FALSE, // normalize
buffer_data // data
);
continue;
case sizeof(Vector4):
gl.Uniform4fv(location, // location
element_count, // count
buffer_data // data
);
continue;
case sizeof(Vector3):
gl.Uniform3fv(location, // location
element_count, // count
buffer_data // data
);
continue;
case sizeof(Vector2):
gl.Uniform2fv(location, // location
element_count, // count
buffer_data // data
);
continue;
case sizeof(Scalar):
gl.Uniform1fv(location, // location
element_count, // count
buffer_data // data
);
continue;
default:
VALIDATION_LOG << "Invalid member size binding: " << member.size;
return false;
}
}
return true;
}
std::optional<size_t> BufferBindingsGLES::BindTextures(
const ProcTableGLES& gl,
const std::vector<TextureAndSampler>& bound_textures,
Range texture_range,
ShaderStage stage,
size_t unit_start_index) {
size_t active_index = unit_start_index;
for (auto i = 0u; i < texture_range.length; i++) {
const TextureAndSampler& data = bound_textures[texture_range.offset + i];
if (data.stage != stage) {
continue;
}
const auto& texture_gles = TextureGLES::Cast(*data.texture.resource);
if (data.texture.GetMetadata() == nullptr) {
VALIDATION_LOG << "No metadata found for texture binding.";
return std::nullopt;
}
auto location = ComputeTextureLocation(data.texture.GetMetadata());
if (location == -1) {
return std::nullopt;
}
//--------------------------------------------------------------------------
/// 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 std::nullopt;
}
gl.ActiveTexture(GL_TEXTURE0 + active_index);
//--------------------------------------------------------------------------
/// Bind the texture.
///
if (!texture_gles.Bind()) {
return std::nullopt;
}
//--------------------------------------------------------------------------
/// If there is a sampler for the texture at the same index, configure the
/// bound texture using that sampler.
///
const auto& sampler_gles = SamplerGLES::Cast(*data.sampler);
if (!sampler_gles.ConfigureBoundTexture(texture_gles, gl)) {
return std::nullopt;
}
//--------------------------------------------------------------------------
/// Set the texture uniform location.
///
gl.Uniform1i(location, active_index);
//--------------------------------------------------------------------------
/// Bump up the active index at binding.
///
active_index++;
}
return active_index;
}
} // namespace impeller