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