blob: 018e70a8f0aaddc717dfe9f71e7f6860e4f9e7c5 [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 <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