| 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_flatbuffers.h"
22 #include "impeller/runtime_stage/runtime_stage_playground.h"
23 #include "runtime_stage_types_flatbuffers.h"
24
25 namespace impeller {
26 namespace testing {
27
28 using RuntimeStageTest = RuntimeStagePlayground;
29 INSTANTIATE_PLAYGROUND_SUITE(RuntimeStageTest);
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());
68 auto stages = RuntimeStage::DecodeRuntimeStages(
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()) {
85 case PlaygroundBackend::kMetal:
86 [[fallthrough]];
87 case PlaygroundBackend::kOpenGLES: {
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 }
220 case PlaygroundBackend::kVulkan: {
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],
361 DescriptorSetLayout{
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));
370 ColorAttachmentDescriptor color0;
371 color0.format = GetContext()->GetCapabilities()->GetDefaultColorFormat();
372 StencilAttachmentDescriptor stencil0;
373 stencil0.stencil_compare = CompareFunction::kEqual;
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
allocation.h
impeller::AllocationSize< 1u >
impeller::PipelineDescriptor
Definition: pipeline_descriptor.h:24
impeller::PipelineDescriptor::SetStencilPixelFormat
PipelineDescriptor & SetStencilPixelFormat(PixelFormat format)
Definition: pipeline_descriptor.cc:145
impeller::PipelineDescriptor::SetVertexDescriptor
PipelineDescriptor & SetVertexDescriptor(std::shared_ptr< VertexDescriptor > vertex_descriptor)
Definition: pipeline_descriptor.cc:96
impeller::PipelineDescriptor::AddStageEntrypoint
PipelineDescriptor & AddStageEntrypoint(std::shared_ptr< const ShaderFunction > function)
Definition: pipeline_descriptor.cc:81
impeller::PipelineDescriptor::SetLabel
PipelineDescriptor & SetLabel(std::string_view label)
Definition: pipeline_descriptor.cc:71
impeller::PipelineDescriptor::SetStencilAttachmentDescriptors
PipelineDescriptor & SetStencilAttachmentDescriptors(std::optional< StencilAttachmentDescriptor > front_and_back)
Definition: pipeline_descriptor.cc:157
impeller::PipelineDescriptor::SetColorAttachmentDescriptor
PipelineDescriptor & SetColorAttachmentDescriptor(size_t index, ColorAttachmentDescriptor desc)
Definition: pipeline_descriptor.cc:110
impeller::RuntimeStage::kVulkanUBOName
static const char * kVulkanUBOName
Definition: runtime_stage.h:23
impeller::RuntimeStage::DecodeRuntimeStages
static absl::StatusOr< Map > DecodeRuntimeStages(const std::shared_ptr< fml::Mapping > &payload)
Definition: runtime_stage.cc:63
impeller::RuntimeStagePlayground
Definition: runtime_stage_playground.h:13
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:59
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller
Definition: allocation.cc:12
impeller::RuntimeShaderStage::kFragment
@ kFragment
impeller::RuntimeStageBackend::kOpenGLES
@ kOpenGLES
impeller::RuntimeStageBackend::kMetal
@ kMetal
impeller::RuntimeStageBackend::kVulkan
@ kVulkan
impeller::RuntimeStageBackend::kSkSL
@ kSkSL
impeller::PlaygroundBackendToRuntimeStageBackend
constexpr RuntimeStageBackend PlaygroundBackendToRuntimeStageBackend(PlaygroundBackend backend)
Definition: playground.h:33
impeller::ToShaderStage
constexpr ShaderStage ToShaderStage(RuntimeShaderStage stage)
Definition: shader_types.h:29
impeller::CompareFunction::kEqual
@ kEqual
Comparison test passes if new_value == current_value.
impeller::ShaderStage::kFragment
@ kFragment
impeller::ShaderStage::kVertex
@ kVertex
impeller::CreateMappingFromAllocation
std::shared_ptr< fml::Mapping > CreateMappingFromAllocation(const std::shared_ptr< Allocation > &allocation)
Creates a mapping from allocation.
Definition: allocation.cc:99
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::PlaygroundBackend::kMetal
@ kMetal
impeller::PlaygroundBackend::kVulkan
@ kVulkan
impeller::VS
LinePipeline::VertexShader VS
Definition: line_contents.cc:13
impeller::DescriptorType::kUniformBuffer
@ kUniformBuffer
impeller::kSampledImage
@ kSampledImage
Definition: runtime_types.h:26
impeller::kFloat
@ kFloat
Definition: runtime_types.h:25
impeller::kStruct
@ kStruct
Definition: runtime_types.h:27
pipeline_descriptor.h
pipeline_library.h
playground.h
runtime_stage.h
runtime_stage_playground.h
runtime_types.h
shader_library.h
shader_types.h
impeller::ColorAttachmentDescriptor
Describe the color attachment that will be used with this pipeline.
Definition: formats.h:522
impeller::ColorAttachmentDescriptor::format
PixelFormat format
Definition: formats.h:523
impeller::DescriptorSetLayout
Definition: shader_types.h:167
impeller::DescriptorSetLayout::binding
uint32_t binding
Definition: shader_types.h:168
impeller::ScopedValidationDisable
Definition: validation.h:56
impeller::StencilAttachmentDescriptor
Definition: formats.h:614
impeller::StencilAttachmentDescriptor::stencil_compare
CompareFunction stencil_compare
Definition: formats.h:620
validation.h