blob: 899cbbb5b7224d25c31dddfc4dc161ed7c1310ac [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/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,
const std::vector<ShaderStageBufferLayout>& layouts) {
std::vector<VertexAttribPointer> vertex_attrib_arrays;
for (auto i = 0u; i < p_inputs.size(); i++) {
const auto& input = p_inputs[i];
const auto& layout = layouts[input.binding];
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.emplace_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;
}
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
);
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) {
for (const auto& buffer : vertex_bindings.buffers) {
if (!BindUniformBuffer(gl, transients_allocator, buffer.view)) {
return false;
}
}
for (const auto& buffer : fragment_bindings.buffers) {
if (!BindUniformBuffer(gl, transients_allocator, buffer.view)) {
return false;
}
}
std::optional<size_t> next_unit_index =
BindTextures(gl, vertex_bindings, ShaderStage::kVertex);
if (!next_unit_index.has_value()) {
return false;
}
if (!BindTextures(gl, fragment_bindings, ShaderStage::kFragment,
*next_unit_index)
.has_value()) {
return false;
}
return true;
}
bool BufferBindingsGLES::UnbindVertexAttributes(const ProcTableGLES& gl) const {
for (const auto& array : vertex_attrib_arrays_) {
gl.DisableVertexAttribArray(array.index);
}
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) {
auto 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.
auto& locations = binding_map_[metadata->name] = {};
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.
locations.push_back(-1);
continue;
}
size_t element_count = member.array_elements.value_or(1);
const auto member_key =
CreateUniformMemberKey(metadata->name, member.name, element_count > 1);
const auto 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,
Allocator& transients_allocator,
const BufferResource& buffer) {
const auto* metadata = buffer.GetMetadata();
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;
}
const auto& locations = ComputeUniformLocations(metadata);
for (auto i = 0u; i < metadata->members.size(); i++) {
const auto& member = metadata->members[i];
auto location = locations[i];
// Void type or inactive uniform.
if (location == -1) {
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);
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, // 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;
}
VALIDATION_LOG << "Size " << member.size
<< " could not be mapped ShaderType::kFloat for key: "
<< member.name;
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.name;
return false;
}
}
return true;
}
std::optional<size_t> BufferBindingsGLES::BindTextures(
const ProcTableGLES& gl,
const Bindings& bindings,
ShaderStage stage,
size_t unit_start_index) {
size_t active_index = unit_start_index;
for (const auto& data : bindings.sampled_images) {
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