| 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"
11 #include "impeller/base/allocation.h"
12 #include "impeller/base/validation.h"
13 #include "impeller/core/runtime_types.h"
14 #include "impeller/core/shader_types.h"
15 #include "impeller/entity/runtime_effect.vert.h"
16 #include "impeller/playground/playground.h"
17 #include "impeller/renderer/pipeline_descriptor.h"
18 #include "impeller/renderer/pipeline_library.h"
19 #include "impeller/renderer/shader_library.h"
20 #include "impeller/runtime_stage/runtime_stage.h"
21 #include "impeller/runtime_stage/runtime_stage_playground.h"
22
23 namespace impeller {
24 namespace testing {
25
26 using RuntimeStageTest = RuntimeStagePlayground;
27 INSTANTIATE_PLAYGROUND_SUITE(RuntimeStageTest);
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());
50 auto stages = RuntimeStage::DecodeRuntimeStages(
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()) {
65 case PlaygroundBackend::kMetal:
66 [[fallthrough]];
67 case PlaygroundBackend::kOpenGLES: {
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 }
200 case PlaygroundBackend::kVulkan: {
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],
335 DescriptorSetLayout{
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));
344 ColorAttachmentDescriptor color0;
345 color0.format = GetContext()->GetCapabilities()->GetDefaultColorFormat();
346 StencilAttachmentDescriptor stencil0;
347 stencil0.stencil_compare = CompareFunction::kEqual;
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