Flutter Impeller
buffer_bindings_gles_unittests.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 
5 #include "flutter/testing/testing.h" // IWYU pragma: keep
6 #include "gtest/gtest.h"
11 #include "impeller/renderer/backend/gles/test/mock_gles.h"
13 
14 namespace impeller {
15 namespace testing {
16 
17 using ::testing::_;
18 
19 TEST(BufferBindingsGLESTest, ToVertexAttribTypeSupportedFormats) {
21  std::optional<GLenum>(GL_FLOAT));
23  std::optional<GLenum>(GL_BYTE));
25  std::optional<GLenum>(GL_UNSIGNED_BYTE));
27  std::optional<GLenum>(GL_SHORT));
29  std::optional<GLenum>(GL_UNSIGNED_SHORT));
30 }
31 
32 TEST(BufferBindingsGLESTest, ToVertexAttribTypeRejectsUnsupportedFormats) {
33  // Half-float and 32-bit integer vertex attributes are not available on the
34  // GLES 2.0 floor.
35  EXPECT_FALSE(ToVertexAttribType(VertexAttributeFormat::kFloat16).has_value());
36  EXPECT_FALSE(ToVertexAttribType(VertexAttributeFormat::kSInt32).has_value());
37  EXPECT_FALSE(
39  EXPECT_FALSE(ToVertexAttribType(VertexAttributeFormat::kInvalid).has_value());
40 }
41 
42 TEST(BufferBindingsGLESTest, BindUniformData) {
43  BufferBindingsGLES bindings;
44  absl::flat_hash_map<std::string, GLint> uniform_bindings;
45  uniform_bindings["SHADERMETADATA.FOOBAR"] = 1;
46  bindings.SetUniformBindings(std::move(uniform_bindings));
47  auto mock_gles_impl = std::make_unique<MockGLESImpl>();
48 
49  EXPECT_CALL(*mock_gles_impl, Uniform1fv(_, _, _)).Times(1);
50 
51  std::shared_ptr<MockGLES> mock_gl = MockGLES::Init(std::move(mock_gles_impl));
52  std::vector<BufferResource> bound_buffers;
53  std::vector<TextureAndSampler> bound_textures;
54 
55  ShaderMetadata shader_metadata = {
56  .name = "shader_metadata",
57  .members = {
59  .name = "foobar",
60  .offset = 0,
61  .size = sizeof(float),
62  .byte_length = sizeof(float),
63  .array_elements = std::nullopt,
64  .float_type = ShaderFloatType::kFloat}}};
65  std::shared_ptr<ReactorGLES> reactor;
66  auto backing_store = std::make_unique<Allocation>();
67  ASSERT_TRUE(backing_store->Truncate(Bytes{sizeof(float)}));
68  DeviceBufferGLES device_buffer(DeviceBufferDescriptor{.size = sizeof(float)},
69  reactor, std::move(backing_store));
70  BufferView buffer_view(&device_buffer, Range(0, sizeof(float)));
71  bound_buffers.push_back(BufferResource(&shader_metadata, buffer_view));
72 
73  EXPECT_TRUE(bindings.BindUniformData(mock_gl->GetProcTable(), bound_textures,
74  bound_buffers, Range{0, 0},
75  Range{0, 1}));
76 }
77 
78 TEST(BufferBindingsGLESTest, BindArrayData) {
79  BufferBindingsGLES bindings;
80  absl::flat_hash_map<std::string, GLint> uniform_bindings;
81  uniform_bindings["SHADERMETADATA.FOOBAR[0]"] = 1;
82  bindings.SetUniformBindings(std::move(uniform_bindings));
83  auto mock_gles_impl = std::make_unique<MockGLESImpl>();
84 
85  EXPECT_CALL(*mock_gles_impl, Uniform1fv(_, _, _)).Times(1);
86 
87  std::shared_ptr<MockGLES> mock_gl = MockGLES::Init(std::move(mock_gles_impl));
88  std::vector<BufferResource> bound_buffers;
89  std::vector<TextureAndSampler> bound_textures;
90 
91  ShaderMetadata shader_metadata = {
92  .name = "shader_metadata",
93  .members = {
95  .name = "foobar",
96  .offset = 0,
97  .size = sizeof(float),
98  .byte_length = sizeof(float) * 4,
99  .array_elements = 4,
100  .float_type = ShaderFloatType::kFloat}}};
101  std::shared_ptr<ReactorGLES> reactor;
102  auto backing_store = std::make_unique<Allocation>();
103  ASSERT_TRUE(backing_store->Truncate(Bytes{sizeof(float) * 4}));
104  DeviceBufferGLES device_buffer(
105  DeviceBufferDescriptor{.size = sizeof(float) * 4}, reactor,
106  std::move(backing_store));
107  BufferView buffer_view(&device_buffer, Range(0, sizeof(float)));
108  bound_buffers.push_back(BufferResource(&shader_metadata, buffer_view));
109 
110  EXPECT_TRUE(bindings.BindUniformData(mock_gl->GetProcTable(), bound_textures,
111  bound_buffers, Range{0, 0},
112  Range{0, 1}));
113 }
114 
115 TEST(BufferBindingsGLESTest, BindUniformDataVerticesAndMatrices) {
116  BufferBindingsGLES bindings;
117  absl::flat_hash_map<std::string, GLint> uniform_bindings;
118  uniform_bindings["SHADERMETADATA.VEC2"] = 1;
119  uniform_bindings["SHADERMETADATA.VEC3"] = 2;
120  uniform_bindings["SHADERMETADATA.VEC4"] = 3;
121  uniform_bindings["SHADERMETADATA.MAT2"] = 4;
122  uniform_bindings["SHADERMETADATA.MAT3"] = 5;
123  uniform_bindings["SHADERMETADATA.MAT4"] = 6;
124  bindings.SetUniformBindings(std::move(uniform_bindings));
125  auto mock_gles_impl = std::make_unique<MockGLESImpl>();
126 
127  EXPECT_CALL(*mock_gles_impl, Uniform2fv(1, 1, _)).Times(1);
128  EXPECT_CALL(*mock_gles_impl, Uniform3fv(2, 1, _)).Times(1);
129  EXPECT_CALL(*mock_gles_impl, Uniform4fv(3, 1, _)).Times(1);
130  EXPECT_CALL(*mock_gles_impl, UniformMatrix2fv(4, 1, GL_FALSE, _)).Times(1);
131  EXPECT_CALL(*mock_gles_impl, UniformMatrix3fv(5, 1, GL_FALSE, _)).Times(1);
132  EXPECT_CALL(*mock_gles_impl, UniformMatrix4fv(6, 1, GL_FALSE, _)).Times(1);
133 
134  std::shared_ptr<MockGLES> mock_gl = MockGLES::Init(std::move(mock_gles_impl));
135  std::vector<BufferResource> bound_buffers;
136  std::vector<TextureAndSampler> bound_textures;
137 
138  auto make_metadata = [](ShaderFloatType float_type, const char* name,
139  size_t size) {
141  .name = name,
142  .offset = 0,
143  .size = size,
144  .byte_length = size,
145  .array_elements = std::nullopt,
146  .float_type = float_type};
147  };
148 
149  ShaderMetadata shader_metadata = {
150  .name = "shader_metadata",
151  .members = {
152  make_metadata(ShaderFloatType::kVec2, "vec2", sizeof(Vector2)),
153  make_metadata(ShaderFloatType::kVec3, "vec3", sizeof(Vector3)),
154  make_metadata(ShaderFloatType::kVec4, "vec4", sizeof(Vector4)),
155  make_metadata(ShaderFloatType::kMat2, "mat2", sizeof(float) * 4),
156  make_metadata(ShaderFloatType::kMat3, "mat3", sizeof(float) * 9),
157  make_metadata(ShaderFloatType::kMat4, "mat4", sizeof(Matrix)),
158  }};
159 
160  std::shared_ptr<ReactorGLES> reactor;
161  auto backing_store = std::make_unique<Allocation>();
162  ASSERT_TRUE(backing_store->Truncate(Bytes{1024})); // Plenty of space
163  DeviceBufferGLES device_buffer(DeviceBufferDescriptor{.size = 1024}, reactor,
164  std::move(backing_store));
165  BufferView buffer_view(&device_buffer, Range(0, 1024));
166  bound_buffers.push_back(BufferResource(&shader_metadata, buffer_view));
167 
168  EXPECT_TRUE(bindings.BindUniformData(mock_gl->GetProcTable(), bound_textures,
169  bound_buffers, Range{0, 0},
170  Range{0, 1}));
171 }
172 
173 // Regression guard: a float uniform that arrives at the GLES backend without
174 // `float_type` populated must be rejected rather than silently dispatched to
175 // the wrong glUniform call. This is the fault mode that motivated the schema
176 // extension; if a future change forgets to populate `float_type` (in the
177 // shader bundle loader, runtime effects, or anywhere else), this test
178 // catches it at unit-test time instead of at runtime.
179 TEST(BufferBindingsGLESTest, BindUniformFailsWithoutFloatType) {
180  BufferBindingsGLES bindings;
181  absl::flat_hash_map<std::string, GLint> uniform_bindings;
182  uniform_bindings["SHADERMETADATA.FOOBAR"] = 1;
183  bindings.SetUniformBindings(std::move(uniform_bindings));
184  auto mock_gles_impl = std::make_unique<MockGLESImpl>();
185  std::shared_ptr<MockGLES> mock_gl = MockGLES::Init(std::move(mock_gles_impl));
186  std::vector<BufferResource> bound_buffers;
187  std::vector<TextureAndSampler> bound_textures;
188 
189  ShaderMetadata shader_metadata = {
190  .name = "shader_metadata",
192  .name = "foobar",
193  .offset = 0,
194  .size = sizeof(float),
195  .byte_length = sizeof(float),
196  .array_elements = std::nullopt,
197  .float_type = std::nullopt}}};
198  std::shared_ptr<ReactorGLES> reactor;
199  auto backing_store = std::make_unique<Allocation>();
200  ASSERT_TRUE(backing_store->Truncate(Bytes{sizeof(float)}));
201  DeviceBufferGLES device_buffer(DeviceBufferDescriptor{.size = sizeof(float)},
202  reactor, std::move(backing_store));
203  BufferView buffer_view(&device_buffer, Range(0, sizeof(float)));
204  bound_buffers.push_back(BufferResource(&shader_metadata, buffer_view));
205 
206  EXPECT_FALSE(bindings.BindUniformData(mock_gl->GetProcTable(), bound_textures,
207  bound_buffers, Range{0, 0},
208  Range{0, 1}));
209 }
210 
211 // An instanced draw reaches per-instance data through instance-rate vertex
212 // attributes. A vertex layout with an instance-rate binding must set a
213 // glVertexAttribDivisor of 1 on that binding's attributes, while a
214 // per-vertex binding keeps a divisor of 0.
215 TEST(BufferBindingsGLESTest, BindVertexAttributesSetsInstanceRateDivisor) {
216  auto mock_gles_impl = std::make_unique<::testing::NiceMock<MockGLESImpl>>();
217  EXPECT_CALL(*mock_gles_impl, VertexAttribDivisor(0, 0)).Times(1);
218  EXPECT_CALL(*mock_gles_impl, VertexAttribDivisor(1, 1)).Times(1);
219  std::shared_ptr<MockGLES> mock_gl = MockGLES::Init(std::move(mock_gles_impl));
220 
221  BufferBindingsGLES bindings;
222 
223  ShaderStageIOSlot per_vertex_input = {
224  .name = "position",
225  .location = 0,
226  .set = 0,
227  .binding = 0,
228  .type = ShaderType::kFloat,
229  .bit_width = sizeof(float) * 8,
230  .vec_size = 2,
231  .columns = 1,
232  .offset = 0,
233  };
234  ShaderStageIOSlot per_instance_input = {
235  .name = "instance_offset",
236  .location = 1,
237  .set = 0,
238  .binding = 1,
239  .type = ShaderType::kFloat,
240  .bit_width = sizeof(float) * 8,
241  .vec_size = 2,
242  .columns = 1,
243  .offset = 0,
244  };
245  std::vector<ShaderStageIOSlot> inputs = {per_vertex_input,
246  per_instance_input};
247  std::vector<ShaderStageBufferLayout> layouts = {
248  ShaderStageBufferLayout{.stride = sizeof(float) * 2,
249  .binding = 0,
250  .input_rate = VertexInputRate::kVertex},
251  ShaderStageBufferLayout{.stride = sizeof(float) * 2,
252  .binding = 1,
253  .input_rate = VertexInputRate::kInstance},
254  };
255 
256  ASSERT_TRUE(bindings.RegisterVertexStageInput(mock_gl->GetProcTable(), inputs,
257  layouts));
258  // Binding 0 is per-vertex (divisor 0); binding 1 is per-instance
259  // (divisor 1).
260  EXPECT_TRUE(bindings.BindVertexAttributes(mock_gl->GetProcTable(),
261  /*binding=*/0, /*vertex_offset=*/0,
262  /*instance=*/0));
263  EXPECT_TRUE(bindings.BindVertexAttributes(mock_gl->GetProcTable(),
264  /*binding=*/1, /*vertex_offset=*/0,
265  /*instance=*/0));
266 }
267 
268 } // namespace testing
269 } // namespace impeller
Sets up stage bindings for single draw call in the OpenGLES backend.
bool BindVertexAttributes(const ProcTableGLES &gl, size_t binding, size_t vertex_offset, size_t instance=0)
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)
uint32_t vec_size
uint32_t columns
TEST(AllocationSizeTest, CanCreateTypedAllocations)
@ kInstance
The binding is read once per instance.
@ kVertex
The binding is read once per vertex. This is the default.
constexpr std::optional< GLenum > ToVertexAttribType(VertexAttributeFormat type)
Definition: formats_gles.h:145
Resource< BufferView > BufferResource
Definition: command.h:55
std::shared_ptr< ReactorGLES > reactor
std::shared_ptr< MockGLES > mock_gl
A 4x4 matrix using column-major storage.
Definition: matrix.h:37