Flutter Impeller
buffer_bindings_gles.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <cstdint>
8 #include <cstring>
9 #include <vector>
10 
20 
21 namespace impeller {
22 
23 // This prefix is used in the names of inputs generated by ANGLE's framebuffer
24 // fetch emulation.
25 static constexpr std::string_view kAngleInputAttachmentPrefix =
26  "ANGLEInputAttachment";
27 
29 
31 
33  const ProcTableGLES& gl,
34  const std::vector<ShaderStageIOSlot>& p_inputs,
35  const std::vector<ShaderStageBufferLayout>& layouts) {
36  std::vector<std::vector<VertexAttribPointer>> vertex_attrib_arrays(
37  layouts.size());
38  // Every layout corresponds to a vertex binding.
39  // As we record, separate the attributes into buckets for each layout in
40  // ascending order. We do this because later on, we'll need to associate each
41  // of the attributes with bound buffers corresponding to the binding.
42  for (auto layout_i = 0u; layout_i < layouts.size(); layout_i++) {
43  const auto& layout = layouts[layout_i];
44  for (const auto& input : p_inputs) {
45  if (input.binding != layout_i) {
46  continue;
47  }
48  VertexAttribPointer attrib;
49  attrib.index = input.location;
50  // Component counts must be 1, 2, 3 or 4. Do that validation now.
51  if (input.vec_size < 1u || input.vec_size > 4u) {
52  return false;
53  }
54  attrib.size = input.vec_size;
55  auto type = ToVertexAttribType(input.GetVertexAttributeFormat());
56  if (!type.has_value()) {
57  return false;
58  }
59  attrib.type = type.value();
60  attrib.normalized = GL_FALSE;
61  attrib.offset = input.offset;
62  attrib.stride = layout.stride;
63  attrib.vertex_attrib_divisor =
64  layout.input_rate == VertexInputRate::kInstance ? 1u : 0u;
65  vertex_attrib_arrays[layout_i].push_back(attrib);
66  }
67  }
68  vertex_attrib_arrays_ = std::move(vertex_attrib_arrays);
69  return true;
70 }
71 
72 static std::string NormalizeUniformKey(const std::string& key) {
73  std::string result;
74  result.reserve(key.length());
75  for (char ch : key) {
76  if (ch != '_') {
77  result.push_back(toupper(ch));
78  }
79  }
80  return result;
81 }
82 
83 static std::string CreateUniformMemberKey(const std::string& struct_name,
84  const std::string& member,
85  bool is_array) {
86  std::string result;
87  result.reserve(struct_name.length() + member.length() + (is_array ? 4 : 1));
88  result += struct_name;
89  if (!member.empty()) {
90  result += '.';
91  result += member;
92  }
93  if (is_array) {
94  result += "[0]";
95  }
96  return NormalizeUniformKey(result);
97 }
98 
99 static std::string CreateUniformMemberKey(
100  const std::string& non_struct_member) {
101  return NormalizeUniformKey(non_struct_member);
102 }
103 
105  GLuint program) {
106  if (!gl.IsProgram(program)) {
107  return false;
108  }
109  program_handle_ = program;
110  if (gl.GetDescription()->GetGlVersion().IsAtLeast(Version{3, 0, 0})) {
111  return ReadUniformsBindingsV3(gl, program);
112  }
113  return ReadUniformsBindingsV2(gl, program);
114 }
115 
116 bool BufferBindingsGLES::ReadUniformsBindingsV3(const ProcTableGLES& gl,
117  GLuint program) {
118  program_handle_ = program;
119  GLint uniform_blocks = 0;
120  gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &uniform_blocks);
121  for (GLint i = 0; i < uniform_blocks; i++) {
122  GLint name_length = 0;
123  gl.GetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_NAME_LENGTH,
124  &name_length);
125 
126  std::vector<GLchar> name;
127  name.resize(name_length);
128  GLint length = 0;
129  gl.GetActiveUniformBlockName(program, i, name_length, &length, name.data());
130 
131  GLuint block_index = gl.GetUniformBlockIndex(program, name.data());
132  gl.UniformBlockBinding(program_handle_, block_index, i);
133 
134  ubo_locations_[std::string{name.data(), static_cast<size_t>(length)}] =
135  std::make_pair(block_index, i);
136  }
137  use_ubo_ = true;
138  return ReadUniformsBindingsV2(gl, program);
139 }
140 
141 bool BufferBindingsGLES::ReadUniformsBindingsV2(const ProcTableGLES& gl,
142  GLuint program) {
143  GLint max_name_size = 0;
144  gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_size);
145 
146  GLint uniform_count = 0;
147  gl.GetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count);
148 
149  // Query the Program for all active uniform locations, and
150  // record this via normalized key.
151  for (GLint i = 0; i < uniform_count; i++) {
152  std::vector<GLchar> name;
153  name.resize(max_name_size);
154  GLsizei written_count = 0u;
155  GLint uniform_var_size = 0u;
156  GLenum uniform_type = GL_FLOAT;
157  // Note: Active uniforms are defined as uniforms that may have an impact on
158  // the output of the shader. Drivers are allowed to (and often do)
159  // optimize out unused uniforms.
160  gl.GetActiveUniform(program, // program
161  i, // index
162  max_name_size, // buffer_size
163  &written_count, // length
164  &uniform_var_size, // size
165  &uniform_type, // type
166  name.data() // name
167  );
168 
169  // Skip unrecognized variables generated by ANGLE.
170  if (gl.GetCapabilities()->IsANGLE()) {
171  if (written_count >=
172  static_cast<GLsizei>(kAngleInputAttachmentPrefix.length()) &&
173  std::string_view(name.data(), kAngleInputAttachmentPrefix.length()) ==
175  continue;
176  }
177  }
178 
179  auto location = gl.GetUniformLocation(program, name.data());
180  if (location == -1) {
181  if (use_ubo_) {
182  continue;
183  }
184  VALIDATION_LOG << "Could not query the location of an active uniform.";
185  return false;
186  }
187  if (written_count <= 0) {
188  VALIDATION_LOG << "Uniform name could not be read for active uniform.";
189  return false;
190  }
191  uniform_locations_[NormalizeUniformKey(std::string{
192  name.data(), static_cast<size_t>(written_count)})] = location;
193  }
194  return true;
195 }
196 
198  size_t binding,
199  size_t vertex_offset,
200  size_t instance) {
201  if (binding >= vertex_attrib_arrays_.size()) {
202  return false;
203  }
204 
205  // For an emulated instanced draw (instance > 0), a binding whose attributes
206  // are all vertex-rate (divisor 0) does not change across instances, so the
207  // state bound for instance 0 still applies. Skip the redundant re-binding.
208  if (instance > 0u) {
209  bool has_instance_rate = false;
210  for (const auto& array : vertex_attrib_arrays_[binding]) {
211  if (array.vertex_attrib_divisor != 0u) {
212  has_instance_rate = true;
213  break;
214  }
215  }
216  if (!has_instance_rate) {
217  return true;
218  }
219  }
220 
221  if (!gl.GetCapabilities()->IsES()) {
222  FML_DCHECK(vertex_array_object_ == 0);
223  gl.GenVertexArrays(1, &vertex_array_object_);
224  gl.BindVertexArray(vertex_array_object_);
225  }
226 
227  for (const auto& array : vertex_attrib_arrays_[binding]) {
228  gl.EnableVertexAttribArray(array.index);
229  // For an emulated instanced draw, an instance-rate attribute is
230  // re-pointed at instance `instance`, since there is no hardware divisor
231  // to advance it. A non-instanced or hardware-instanced draw passes
232  // instance 0 and lets the divisor (if any) do the stepping.
233  size_t attribute_offset = vertex_offset + array.offset;
234  if (array.vertex_attrib_divisor != 0u) {
235  attribute_offset += instance * static_cast<size_t>(array.stride);
236  }
237  gl.VertexAttribPointer(array.index, // index
238  array.size, // size (must be 1, 2, 3, or 4)
239  array.type, // type
240  array.normalized, // normalized
241  array.stride, // stride
242  reinterpret_cast<const GLvoid*>(
243  static_cast<uintptr_t>(attribute_offset)) // ptr
244  );
245  // Set the instancing divisor when the driver supports it. It is core
246  // on ES 3.0+ and comes from GL_EXT_instanced_arrays on ES 2.0. When
247  // unavailable, only per-vertex (divisor 0) bindings are possible,
248  // which is the default. Setting it for every attribute (including
249  // divisor 0) also clears any stale divisor left by a prior pipeline,
250  // which matters on ES, where there is no vertex array object.
251  if (gl.VertexAttribDivisor.IsAvailable()) {
252  gl.VertexAttribDivisor(array.index, array.vertex_attrib_divisor);
253  } else if (gl.VertexAttribDivisorEXT.IsAvailable()) {
254  gl.VertexAttribDivisorEXT(array.index, array.vertex_attrib_divisor);
255  }
256  }
257 
258  return true;
259 }
260 
262  const ProcTableGLES& gl,
263  const std::vector<TextureAndSampler>& bound_textures,
264  const std::vector<BufferResource>& bound_buffers,
265  Range texture_range,
266  Range buffer_range) {
267  for (auto i = 0u; i < buffer_range.length; i++) {
268  if (!BindUniformBuffer(gl, bound_buffers[buffer_range.offset + i])) {
269  return false;
270  }
271  }
272  std::optional<size_t> next_unit_index =
273  BindTextures(gl, bound_textures, texture_range, ShaderStage::kVertex);
274  if (!next_unit_index.has_value()) {
275  return false;
276  }
277  if (!BindTextures(gl, bound_textures, texture_range, ShaderStage::kFragment,
278  *next_unit_index)
279  .has_value()) {
280  return false;
281  }
282 
283  return true;
284 }
285 
287  for (const auto& array : vertex_attrib_arrays_) {
288  for (const auto& attribute : array) {
289  gl.DisableVertexAttribArray(attribute.index);
290  }
291  }
292  if (!gl.GetCapabilities()->IsES()) {
293  gl.DeleteVertexArrays(1, &vertex_array_object_);
294  vertex_array_object_ = 0;
295  }
296 
297  return true;
298 }
299 
300 GLint BufferBindingsGLES::ComputeTextureLocation(
301  const ShaderMetadata* metadata) {
302  auto location = binding_map_.find(metadata->name);
303  if (location != binding_map_.end()) {
304  return location->second[0];
305  }
306  auto& locations = binding_map_[metadata->name] = {};
307  auto computed_location =
308  uniform_locations_.find(CreateUniformMemberKey(metadata->name));
309  if (computed_location == uniform_locations_.end()) {
310  locations.push_back(-1);
311  } else {
312  locations.push_back(computed_location->second);
313  }
314  return locations[0];
315 }
316 
317 const std::vector<GLint>& BufferBindingsGLES::ComputeUniformLocations(
318  const ShaderMetadata* metadata) {
319  BindingMap::iterator location = binding_map_.find(metadata->name);
320  if (location != binding_map_.end()) {
321  return location->second;
322  }
323 
324  // For each metadata member, look up the binding location and record
325  // it in the binding map.
326  std::vector<GLint>& locations = binding_map_[metadata->name] = {};
327  locations.reserve(metadata->members.size());
328  for (const ShaderStructMemberMetadata& member : metadata->members) {
329  if (member.type == ShaderType::kVoid) {
330  // Void types are used for padding. We are obviously not going to find
331  // mappings for these. Keep going.
332  locations.push_back(-1);
333  continue;
334  }
335 
336  const std::string member_key = CreateUniformMemberKey(
337  metadata->name, member.name, member.array_elements.has_value());
338  const absl::flat_hash_map<std::string, GLint>::iterator computed_location =
339  uniform_locations_.find(member_key);
340  if (computed_location == uniform_locations_.end()) {
341  // Uniform was not active.
342  locations.push_back(-1);
343  continue;
344  }
345  locations.push_back(computed_location->second);
346  }
347  return locations;
348 }
349 
350 bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl,
351  const BufferResource& buffer) {
352  const ShaderMetadata* metadata = buffer.GetMetadata();
353  const DeviceBuffer* device_buffer = buffer.resource.GetBuffer();
354  if (!device_buffer) {
355  VALIDATION_LOG << "Device buffer not found.";
356  return false;
357  }
358  const DeviceBufferGLES& device_buffer_gles =
359  DeviceBufferGLES::Cast(*device_buffer);
360 
361  if (use_ubo_) {
362  return BindUniformBufferV3(gl, buffer.resource, metadata,
363  device_buffer_gles);
364  }
365  return BindUniformBufferV2(gl, buffer.resource, metadata, device_buffer_gles);
366 }
367 
368 bool BufferBindingsGLES::BindUniformBufferV3(
369  const ProcTableGLES& gl,
370  const BufferView& buffer,
371  const ShaderMetadata* metadata,
372  const DeviceBufferGLES& device_buffer_gles) {
373  absl::flat_hash_map<std::string, std::pair<GLint, GLuint>>::iterator it =
374  ubo_locations_.find(metadata->name);
375  if (it == ubo_locations_.end()) {
376  // This should only happen if we have GLESv3 but are using v2 shaders,
377  // as GLESv3 shaders compiled by impeller always have
378  // **named** uniform buffer blocks
379  return BindUniformBufferV2(gl, buffer, metadata, device_buffer_gles);
380  }
381  const auto& [block_index, binding_point] = it->second;
382  if (!device_buffer_gles.BindAndUploadDataIfNecessary(
384  return false;
385  }
386  auto handle = device_buffer_gles.GetHandle();
387  if (!handle.has_value()) {
388  return false;
389  }
390  gl.BindBufferRange(GL_UNIFORM_BUFFER, binding_point, handle.value(),
391  buffer.GetRange().offset, buffer.GetRange().length);
392  return true;
393 }
394 
395 bool BufferBindingsGLES::BindUniformBufferV2(
396  const ProcTableGLES& gl,
397  const BufferView& buffer,
398  const ShaderMetadata* metadata,
399  const DeviceBufferGLES& device_buffer_gles) {
400  const uint8_t* buffer_ptr =
401  device_buffer_gles.GetBufferData() + buffer.GetRange().offset;
402 
403  if (metadata->members.empty()) {
404  VALIDATION_LOG << "Uniform buffer had no members. This is currently "
405  "unsupported in the OpenGL ES backend. Use a uniform "
406  "buffer block.";
407  return false;
408  }
409 
410  const std::vector<GLint>& locations = ComputeUniformLocations(metadata);
411  for (size_t i = 0u; i < metadata->members.size(); i++) {
412  const ShaderStructMemberMetadata& member = metadata->members[i];
413  GLint location = locations[i];
414  // Void type or inactive uniform.
415  if (location == -1 || member.type == ShaderType::kVoid) {
416  continue;
417  }
418 
419  size_t element_count = member.array_elements.value_or(1);
420  size_t element_stride = member.byte_length / element_count;
421  auto* buffer_data =
422  reinterpret_cast<const GLfloat*>(buffer_ptr + member.offset);
423 
424  // When binding uniform arrays, the elements must be contiguous. Copy
425  // the uniforms to a temp buffer to eliminate any padding needed by the
426  // other backends if the array elements have padding.
427  std::vector<uint8_t> array_element_buffer;
428  if (element_count > 1 && element_stride != member.size) {
429  array_element_buffer.resize(member.size * element_count);
430  for (size_t element_i = 0; element_i < element_count; element_i++) {
431  std::memcpy(array_element_buffer.data() + element_i * member.size,
432  reinterpret_cast<const char*>(buffer_data) +
433  element_i * element_stride,
434  member.size);
435  }
436  buffer_data =
437  reinterpret_cast<const GLfloat*>(array_element_buffer.data());
438  }
439 
440  if (member.type != ShaderType::kFloat) {
441  VALIDATION_LOG << "Unsupported uniform data type for key: " << member.name
442  << ", has type " << static_cast<int>(member.type)
443  << ". Only float uniforms are supported.";
444  return false;
445  }
446 
447  if (!member.float_type.has_value()) {
448  VALIDATION_LOG << "Float uniform should have a float type.";
449  return false;
450  }
451 
452  switch (member.float_type.value()) {
454  gl.Uniform1fv(location, element_count, buffer_data);
455  break;
457  gl.Uniform2fv(location, element_count, buffer_data);
458  break;
460  gl.Uniform3fv(location, element_count, buffer_data);
461  break;
463  gl.Uniform4fv(location, element_count, buffer_data);
464  break;
466  gl.UniformMatrix2fv(location, element_count, GL_FALSE, buffer_data);
467  break;
469  gl.UniformMatrix3fv(location, element_count, GL_FALSE, buffer_data);
470  break;
472  gl.UniformMatrix4fv(location, element_count, GL_FALSE, buffer_data);
473  break;
474  }
475  }
476  return true;
477 }
478 
479 std::optional<size_t> BufferBindingsGLES::BindTextures(
480  const ProcTableGLES& gl,
481  const std::vector<TextureAndSampler>& bound_textures,
482  Range texture_range,
483  ShaderStage stage,
484  size_t unit_start_index) {
485  size_t active_index = unit_start_index;
486  for (auto i = 0u; i < texture_range.length; i++) {
487  const TextureAndSampler& data = bound_textures[texture_range.offset + i];
488  if (data.stage != stage) {
489  continue;
490  }
491  const auto& texture_gles = TextureGLES::Cast(*data.texture.resource);
492  if (data.texture.GetMetadata() == nullptr) {
493  VALIDATION_LOG << "No metadata found for texture binding.";
494  return std::nullopt;
495  }
496 
497  auto location = ComputeTextureLocation(data.texture.GetMetadata());
498  if (location == -1) {
499  // The texture binding was optimized out of the shader. Continue.
500  continue;
501  }
502 
503  //--------------------------------------------------------------------------
504  /// Set the active texture unit.
505  ///
506  if (active_index >= gl.GetCapabilities()->GetMaxTextureUnits(stage)) {
507  VALIDATION_LOG << "Texture units specified exceed the capabilities for "
508  "this shader stage.";
509  return std::nullopt;
510  }
511  gl.ActiveTexture(GL_TEXTURE0 + active_index);
512 
513  //--------------------------------------------------------------------------
514  /// Bind the texture.
515  ///
516  // The `texture_gles` reference is bound `const` because it is reached
517  // via a const view into the bound texture list, but `Bind()` mutates
518  // GLES-specific lazy-init bookkeeping (`slice_mip_initialized_`,
519  // `fence_`). The mutation is implementation detail of the GLES backend
520  // and is invisible to the higher abstraction; the `const_cast` is
521  // confined to this one call site.
522  if (!const_cast<TextureGLES&>(texture_gles).Bind()) {
523  return std::nullopt;
524  }
525 
526  //--------------------------------------------------------------------------
527  /// If there is a sampler for the texture at the same index, configure the
528  /// bound texture using that sampler.
529  ///
530  const auto& sampler_gles = SamplerGLES::Cast(*data.sampler);
531  if (!sampler_gles.ConfigureBoundTexture(texture_gles, gl)) {
532  return std::nullopt;
533  }
534 
535  //--------------------------------------------------------------------------
536  /// Set the texture uniform location.
537  ///
538  gl.Uniform1i(location, active_index);
539 
540  //--------------------------------------------------------------------------
541  /// Bump up the active index at binding.
542  ///
543  active_index++;
544  }
545  return active_index;
546 }
547 
548 } // namespace impeller
static DeviceBufferGLES & Cast(DeviceBuffer &base)
Definition: backend_cast.h:13
bool BindVertexAttributes(const ProcTableGLES &gl, size_t binding, size_t vertex_offset, size_t instance=0)
bool ReadUniformsBindings(const ProcTableGLES &gl, GLuint program)
bool UnbindVertexAttributes(const ProcTableGLES &gl)
bool RegisterVertexStageInput(const ProcTableGLES &gl, const std::vector< ShaderStageIOSlot > &inputs, const std::vector< ShaderStageBufferLayout > &layouts)
bool BindUniformData(const ProcTableGLES &gl, const std::vector< TextureAndSampler > &bound_textures, const std::vector< BufferResource > &bound_buffers, Range texture_range, Range buffer_range)
const std::shared_ptr< const CapabilitiesGLES > & GetCapabilities() const
const DescriptionGLES * GetDescription() const
uint32_t location
@ kInstance
The binding is read once per instance.
static std::string CreateUniformMemberKey(const std::string &struct_name, const std::string &member, bool is_array)
static constexpr std::string_view kAngleInputAttachmentPrefix
constexpr std::optional< GLenum > ToVertexAttribType(VertexAttributeFormat type)
Definition: formats_gles.h:145
Resource< BufferView > BufferResource
Definition: command.h:55
static std::string NormalizeUniformKey(const std::string &key)
static bool Bind(PassBindingsCacheMTL &pass, ShaderStage stage, size_t bind_index, const BufferView &view)
size_t length
Definition: range.h:15
size_t offset
Definition: range.h:14
constexpr bool IsAtLeast(const Version &other) const
Definition: version.h:31
#define VALIDATION_LOG
Definition: validation.h:91