Flutter Impeller
runtime_stage_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 <future>
6 
7 #include "flutter/fml/make_copyable.h"
8 #include "flutter/testing/testing.h"
9 #include "gmock/gmock.h"
10 #include "gtest/gtest.h"
15 #include "impeller/entity/runtime_effect.vert.h"
21 #include "impeller/runtime_stage/runtime_stage_flatbuffers.h"
23 #include "runtime_stage_types_flatbuffers.h"
24 #include "third_party/abseil-cpp/absl/status/status_matchers.h"
25 
26 namespace impeller {
27 namespace testing {
28 
31 
32 TEST_P(RuntimeStageTest, CanReadValidBlob) {
33  const std::shared_ptr<fml::Mapping> fixture =
34  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
35  ASSERT_TRUE(fixture);
36  ASSERT_GT(fixture->GetSize(), 0u);
37  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
38  ABSL_ASSERT_OK(stages);
39  auto stage =
40  stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
41  ASSERT_TRUE(stage);
42  ASSERT_EQ(stage->GetShaderStage(), RuntimeShaderStage::kFragment);
43 }
44 
45 TEST_P(RuntimeStageTest, RejectInvalidFormatVersion) {
46  flatbuffers::FlatBufferBuilder builder;
47  fb::RuntimeStagesBuilder stages_builder(builder);
48  stages_builder.add_format_version(0);
49  auto stages = stages_builder.Finish();
50  builder.Finish(stages, fb::RuntimeStagesIdentifier());
51  auto mapping = std::make_shared<fml::NonOwnedMapping>(
52  builder.GetBufferPointer(), builder.GetSize());
53  auto runtime_stages = RuntimeStage::DecodeRuntimeStages(mapping);
54  EXPECT_FALSE(runtime_stages.ok());
55  EXPECT_EQ(runtime_stages.status().code(), absl::StatusCode::kInvalidArgument);
56 }
57 
58 TEST_P(RuntimeStageTest, CanRejectInvalidBlob) {
59  ScopedValidationDisable disable_validation;
60  const std::shared_ptr<fml::Mapping> fixture =
61  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
62  ASSERT_TRUE(fixture);
63  auto junk_allocation = std::make_shared<Allocation>();
64  ASSERT_TRUE(junk_allocation->Truncate(Bytes{fixture->GetSize()}, false));
65  // Not meant to be secure. Just reject obviously bad blobs using magic
66  // numbers.
67  ::memset(junk_allocation->GetBuffer(), 127,
68  junk_allocation->GetLength().GetByteSize());
70  CreateMappingFromAllocation(junk_allocation));
71  ASSERT_FALSE(stages.ok());
72 }
73 
74 TEST_P(RuntimeStageTest, CanReadUniforms) {
75  const std::shared_ptr<fml::Mapping> fixture =
76  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
77  ASSERT_TRUE(fixture);
78  ASSERT_GT(fixture->GetSize(), 0u);
79  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
80  ABSL_ASSERT_OK(stages);
81  auto stage =
82  stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
83 
84  ASSERT_TRUE(stage);
85  switch (GetBackend()) {
87  [[fallthrough]];
89  ASSERT_EQ(stage->GetUniforms().size(), 17u);
90  {
91  auto uni = stage->GetUniform("u_color");
92  ASSERT_NE(uni, nullptr);
93  EXPECT_EQ(uni->dimensions.rows, 4u);
94  EXPECT_EQ(uni->dimensions.cols, 1u);
95  EXPECT_EQ(uni->location, 0u);
96  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
97  }
98  {
99  auto uni = stage->GetUniform("u_alpha");
100  ASSERT_NE(uni, nullptr);
101  EXPECT_EQ(uni->dimensions.rows, 1u);
102  EXPECT_EQ(uni->dimensions.cols, 1u);
103  EXPECT_EQ(uni->location, 1u);
104  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
105  }
106  {
107  auto uni = stage->GetUniform("u_sparkle_color");
108  ASSERT_NE(uni, nullptr);
109  EXPECT_EQ(uni->dimensions.rows, 4u);
110  EXPECT_EQ(uni->dimensions.cols, 1u);
111  EXPECT_EQ(uni->location, 2u);
112  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
113  }
114  {
115  auto uni = stage->GetUniform("u_sparkle_alpha");
116  ASSERT_NE(uni, nullptr);
117  EXPECT_EQ(uni->dimensions.rows, 1u);
118  EXPECT_EQ(uni->dimensions.cols, 1u);
119  EXPECT_EQ(uni->location, 3u);
120  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
121  }
122  {
123  auto uni = stage->GetUniform("u_blur");
124  ASSERT_NE(uni, nullptr);
125  EXPECT_EQ(uni->dimensions.rows, 1u);
126  EXPECT_EQ(uni->dimensions.cols, 1u);
127  EXPECT_EQ(uni->location, 4u);
128  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
129  }
130  {
131  auto uni = stage->GetUniform("u_radius_scale");
132  ASSERT_NE(uni, nullptr);
133  EXPECT_EQ(uni->dimensions.rows, 1u);
134  EXPECT_EQ(uni->dimensions.cols, 1u);
135  EXPECT_EQ(uni->location, 6u);
136  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
137  }
138  {
139  auto uni = stage->GetUniform("u_max_radius");
140  ASSERT_NE(uni, nullptr);
141  EXPECT_EQ(uni->dimensions.rows, 1u);
142  EXPECT_EQ(uni->dimensions.cols, 1u);
143  EXPECT_EQ(uni->location, 7u);
144  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
145  }
146  {
147  auto uni = stage->GetUniform("u_resolution_scale");
148  ASSERT_NE(uni, nullptr);
149  EXPECT_EQ(uni->dimensions.rows, 2u);
150  EXPECT_EQ(uni->dimensions.cols, 1u);
151  EXPECT_EQ(uni->location, 8u);
152  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
153  }
154  {
155  auto uni = stage->GetUniform("u_noise_scale");
156  ASSERT_NE(uni, nullptr);
157  EXPECT_EQ(uni->dimensions.rows, 2u);
158  EXPECT_EQ(uni->dimensions.cols, 1u);
159  EXPECT_EQ(uni->location, 9u);
160  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
161  }
162  {
163  auto uni = stage->GetUniform("u_noise_phase");
164  ASSERT_NE(uni, nullptr);
165  EXPECT_EQ(uni->dimensions.rows, 1u);
166  EXPECT_EQ(uni->dimensions.cols, 1u);
167  EXPECT_EQ(uni->location, 10u);
168  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
169  }
170 
171  {
172  auto uni = stage->GetUniform("u_circle1");
173  ASSERT_NE(uni, nullptr);
174  EXPECT_EQ(uni->dimensions.rows, 2u);
175  EXPECT_EQ(uni->dimensions.cols, 1u);
176  EXPECT_EQ(uni->location, 11u);
177  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
178  }
179  {
180  auto uni = stage->GetUniform("u_circle2");
181  ASSERT_NE(uni, nullptr);
182  EXPECT_EQ(uni->dimensions.rows, 2u);
183  EXPECT_EQ(uni->dimensions.cols, 1u);
184  EXPECT_EQ(uni->location, 12u);
185  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
186  }
187  {
188  auto uni = stage->GetUniform("u_circle3");
189  ASSERT_NE(uni, nullptr);
190  EXPECT_EQ(uni->dimensions.rows, 2u);
191  EXPECT_EQ(uni->dimensions.cols, 1u);
192  EXPECT_EQ(uni->location, 13u);
193  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
194  }
195  {
196  auto uni = stage->GetUniform("u_rotation1");
197  ASSERT_NE(uni, nullptr);
198  EXPECT_EQ(uni->dimensions.rows, 2u);
199  EXPECT_EQ(uni->dimensions.cols, 1u);
200  EXPECT_EQ(uni->location, 14u);
201  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
202  }
203  {
204  auto uni = stage->GetUniform("u_rotation2");
205  ASSERT_NE(uni, nullptr);
206  EXPECT_EQ(uni->dimensions.rows, 2u);
207  EXPECT_EQ(uni->dimensions.cols, 1u);
208  EXPECT_EQ(uni->location, 15u);
209  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
210  }
211  {
212  auto uni = stage->GetUniform("u_rotation3");
213  ASSERT_NE(uni, nullptr);
214  EXPECT_EQ(uni->dimensions.rows, 2u);
215  EXPECT_EQ(uni->dimensions.cols, 1u);
216  EXPECT_EQ(uni->location, 16u);
217  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
218  }
219  break;
220  }
222  EXPECT_EQ(stage->GetUniforms().size(), 1u);
223  auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
224  ASSERT_TRUE(uni);
225  EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
226  EXPECT_EQ(uni->struct_float_count, 32u);
227 
228  // There are 36 4 byte chunks in the UBO: 32 for the 32 floats, and 4 for
229  // padding. Initialize a vector as if they'll all be floats, then manually
230  // set the few padding bytes. If the shader changes, the padding locations
231  // will change as well. For example, if `u_alpha` was moved to the end,
232  // three bytes of padding could potentially be dropped - or if some of the
233  // scalar floats were changed to vec2 or vec4s, or if any vec3s are
234  // introduced.
235  // This means 36 * 4 = 144 bytes total.
236 
237  EXPECT_EQ(uni->GetSize(), 144u);
238  std::vector<uint8_t> layout(uni->GetSize() / sizeof(float), 1);
239  layout[5] = 0;
240  layout[6] = 0;
241  layout[7] = 0;
242  layout[23] = 0;
243 
244  EXPECT_THAT(uni->struct_layout, ::testing::ElementsAreArray(layout));
245  break;
246  }
247  }
248 }
249 
250 TEST_P(RuntimeStageTest, CanReadUniformsSamplerBeforeUBO) {
251  if (GetBackend() != PlaygroundBackend::kVulkan) {
252  GTEST_SKIP() << "Test only relevant for Vulkan";
253  }
254  const std::shared_ptr<fml::Mapping> fixture =
255  flutter::testing::OpenFixtureAsMapping(
256  "uniforms_and_sampler_1.frag.iplr");
257  ASSERT_TRUE(fixture);
258  ASSERT_GT(fixture->GetSize(), 0u);
259  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
260  ABSL_ASSERT_OK(stages);
261  auto stage =
262  stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
263 
264  EXPECT_EQ(stage->GetUniforms().size(), 2u);
265  auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
266  ASSERT_TRUE(uni);
267  // Struct must be offset at 65.
268  EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
269  EXPECT_EQ(uni->binding, 65u);
270  // Sampler should be offset at 64 but due to current bug
271  // has offset of 0, the correct offset is computed at runtime.
272  auto sampler_uniform = stage->GetUniform("u_texture");
273  EXPECT_EQ(sampler_uniform->type, RuntimeUniformType::kSampledImage);
274  EXPECT_EQ(sampler_uniform->binding, 64u);
275 }
276 
277 TEST_P(RuntimeStageTest, CanReadUniformsSamplerAfterUBO) {
278  if (GetBackend() != PlaygroundBackend::kVulkan) {
279  GTEST_SKIP() << "Test only relevant for Vulkan";
280  }
281  const std::shared_ptr<fml::Mapping> fixture =
282  flutter::testing::OpenFixtureAsMapping(
283  "uniforms_and_sampler_2.frag.iplr");
284  ASSERT_TRUE(fixture);
285  ASSERT_GT(fixture->GetSize(), 0u);
286  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
287  ABSL_ASSERT_OK(stages);
288  auto stage =
289  stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
290 
291  EXPECT_EQ(stage->GetUniforms().size(), 2u);
292  auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
293  ASSERT_TRUE(uni);
294  // Struct must be offset at 45.
295  EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
296  EXPECT_EQ(uni->binding, 64u);
297  // Sampler should be offset at 64 but due to current bug
298  // has offset of 0, the correct offset is computed at runtime.
299  auto sampler_uniform = stage->GetUniform("u_texture");
300  EXPECT_EQ(sampler_uniform->type, RuntimeUniformType::kSampledImage);
301  EXPECT_EQ(sampler_uniform->binding, 65u);
302 }
303 
304 TEST_P(RuntimeStageTest, CanRegisterStage) {
305  const std::shared_ptr<fml::Mapping> fixture =
306  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
307  ASSERT_TRUE(fixture);
308  ASSERT_GT(fixture->GetSize(), 0u);
309  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
310  ABSL_ASSERT_OK(stages);
311  auto stage =
312  stages.value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
313  ASSERT_TRUE(stage);
314  std::promise<bool> registration;
315  auto future = registration.get_future();
316  auto library = GetContext()->GetShaderLibrary();
317  library->RegisterFunction(
318  stage->GetEntrypoint(), //
319  ToShaderStage(stage->GetShaderStage()), //
320  stage->GetCodeMapping(), //
321  fml::MakeCopyable([reg = std::move(registration)](bool result) mutable {
322  reg.set_value(result);
323  }));
324  ASSERT_TRUE(future.get());
325  {
326  auto function =
327  library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
328  ASSERT_NE(function, nullptr);
329  }
330 
331  // Check if unregistering works.
332 
333  library->UnregisterFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
334  {
335  auto function =
336  library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
337  ASSERT_EQ(function, nullptr);
338  }
339 }
340 
341 TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) {
342  auto stages_result = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr");
343  ABSL_ASSERT_OK(stages_result);
344  auto stage =
345  stages_result
346  .value()[PlaygroundBackendToRuntimeStageBackend(GetBackend())];
347 
348  ASSERT_TRUE(stage);
349  ASSERT_NE(stage, nullptr);
350  ASSERT_TRUE(RegisterStage(*stage));
351  auto library = GetContext()->GetShaderLibrary();
352  using VS = RuntimeEffectVertexShader;
353  PipelineDescriptor desc;
354  desc.SetLabel("Runtime Stage InkSparkle");
355  desc.AddStageEntrypoint(
356  library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex));
357  desc.AddStageEntrypoint(
358  library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment));
359  auto vertex_descriptor = std::make_shared<VertexDescriptor>();
360  vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs,
361  VS::kInterleavedBufferLayout);
362 
363  std::array<DescriptorSetLayout, 2> descriptor_set_layouts = {
364  VS::kDescriptorSetLayouts[0],
366  .binding = 64u,
367  .descriptor_type = DescriptorType::kUniformBuffer,
368  .shader_stage = ShaderStage::kFragment,
369  },
370  };
371  vertex_descriptor->RegisterDescriptorSetLayouts(descriptor_set_layouts);
372 
373  desc.SetVertexDescriptor(std::move(vertex_descriptor));
375  color0.format = GetContext()->GetCapabilities()->GetDefaultColorFormat();
378  desc.SetColorAttachmentDescriptor(0u, color0);
379  desc.SetStencilAttachmentDescriptors(stencil0);
380  const auto stencil_fmt =
381  GetContext()->GetCapabilities()->GetDefaultStencilFormat();
382  desc.SetStencilPixelFormat(stencil_fmt);
383  auto pipeline = GetContext()->GetPipelineLibrary()->GetPipeline(desc).Get();
384  ASSERT_NE(pipeline, nullptr);
385 }
386 
387 TEST_P(RuntimeStageTest, ContainsExpectedShaderTypes) {
388  auto stages_result = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr");
389  ABSL_ASSERT_OK(stages_result);
390  auto stages = stages_result.value();
391  // Right now, SkSL gets implicitly bundled regardless of what the build rule
392  // for this test requested. After
393  // https://github.com/flutter/flutter/issues/138919, this may require a build
394  // rule change or a new test.
395  EXPECT_TRUE(stages[RuntimeStageBackend::kSkSL]);
396 
397  EXPECT_TRUE(stages[RuntimeStageBackend::kOpenGLES]);
398  EXPECT_TRUE(stages[RuntimeStageBackend::kMetal]);
399  EXPECT_TRUE(stages[RuntimeStageBackend::kVulkan]);
400 }
401 
402 } // namespace testing
403 } // namespace impeller
PipelineDescriptor & SetStencilPixelFormat(PixelFormat format)
PipelineDescriptor & SetVertexDescriptor(std::shared_ptr< VertexDescriptor > vertex_descriptor)
PipelineDescriptor & AddStageEntrypoint(std::shared_ptr< const ShaderFunction > function)
PipelineDescriptor & SetLabel(std::string_view label)
PipelineDescriptor & SetStencilAttachmentDescriptors(std::optional< StencilAttachmentDescriptor > front_and_back)
PipelineDescriptor & SetColorAttachmentDescriptor(size_t index, ColorAttachmentDescriptor desc)
static const char * kVulkanUBOName
Definition: runtime_stage.h:23
static absl::StatusOr< Map > DecodeRuntimeStages(const std::shared_ptr< fml::Mapping > &payload)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
constexpr ShaderStage ToShaderStage(RuntimeShaderStage stage)
Definition: shader_types.h:29
@ kEqual
Comparison test passes if new_value == current_value.
std::shared_ptr< fml::Mapping > CreateMappingFromAllocation(const std::shared_ptr< Allocation > &allocation)
Creates a mapping from allocation.
Definition: allocation.cc:99
LinePipeline::VertexShader VS
Describe the color attachment that will be used with this pipeline.
Definition: formats.h:522