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