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