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 <cstddef>
6 #include <future>
7 
8 #include "flutter/fml/make_copyable.h"
9 #include "flutter/testing/testing.h"
10 #include "gmock/gmock.h"
11 #include "gtest/gtest.h"
16 #include "impeller/entity/runtime_effect.vert.h"
23 #include "impeller/runtime_stage/runtime_stage_flatbuffers.h"
25 #include "runtime_stage_types_flatbuffers.h"
26 #include "third_party/abseil-cpp/absl/status/status_matchers.h"
27 
28 namespace impeller {
29 namespace testing {
30 
33 
34 TEST_P(RuntimeStageTest, CanReadValidBlob) {
35  const std::shared_ptr<fml::Mapping> fixture =
36  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
37  ASSERT_TRUE(fixture);
38  ASSERT_GT(fixture->GetSize(), 0u);
39  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
40  ABSL_ASSERT_OK(stages);
41  auto stage = stages.value()[GetRuntimeStageBackend()];
42  ASSERT_TRUE(stage);
43  ASSERT_EQ(stage->GetShaderStage(), RuntimeShaderStage::kFragment);
44 }
45 
46 TEST_P(RuntimeStageTest, RejectInvalidFormatVersion) {
47  flatbuffers::FlatBufferBuilder builder;
48  fb::RuntimeStagesBuilder stages_builder(builder);
49  stages_builder.add_format_version(0);
50  auto stages = stages_builder.Finish();
51  builder.Finish(stages, fb::RuntimeStagesIdentifier());
52  auto mapping = std::make_shared<fml::NonOwnedMapping>(
53  builder.GetBufferPointer(), builder.GetSize());
54  auto runtime_stages = RuntimeStage::DecodeRuntimeStages(mapping);
55  EXPECT_FALSE(runtime_stages.ok());
56  EXPECT_EQ(runtime_stages.status().code(), absl::StatusCode::kInvalidArgument);
57 }
58 
59 TEST_P(RuntimeStageTest, CanRejectInvalidBlob) {
60  ScopedValidationDisable disable_validation;
61  const std::shared_ptr<fml::Mapping> fixture =
62  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
63  ASSERT_TRUE(fixture);
64  auto junk_allocation = std::make_shared<Allocation>();
65  ASSERT_TRUE(junk_allocation->Truncate(Bytes{fixture->GetSize()}, false));
66  // Not meant to be secure. Just reject obviously bad blobs using magic
67  // numbers.
68  ::memset(junk_allocation->GetBuffer(), 127,
69  junk_allocation->GetLength().GetByteSize());
71  CreateMappingFromAllocation(junk_allocation));
72  ASSERT_FALSE(stages.ok());
73 }
74 
75 TEST_P(RuntimeStageTest, RejectsCorruptBufferWithValidIdentifier) {
76  ScopedValidationDisable disable_validation;
77  // Construct a buffer with a valid "IPLR" file identifier at bytes 4-7
78  // but a root table offset that points beyond the buffer. This passes
79  // the identifier check but fails FlatBuffer structural verification.
80  auto data = std::make_shared<std::vector<uint8_t>>(32, 0);
81  (*data)[4] = 'I';
82  (*data)[5] = 'P';
83  (*data)[6] = 'L';
84  (*data)[7] = 'R';
85  // Root offset (little-endian uint32 at offset 0) pointing out of bounds.
86  (*data)[0] = 0xFF;
87  (*data)[1] = 0xFF;
88 
89  auto mapping = std::make_shared<fml::NonOwnedMapping>(
90  data->data(), data->size(), [data](auto, auto) {});
91  auto stages = RuntimeStage::DecodeRuntimeStages(mapping);
92  ASSERT_FALSE(stages.ok());
93  EXPECT_EQ(stages.status().code(), absl::StatusCode::kInvalidArgument);
94 }
95 
96 TEST_P(RuntimeStageTest, CanReadUniforms) {
97  const std::shared_ptr<fml::Mapping> fixture =
98  flutter::testing::OpenFixtureAsMapping(
99  "all_supported_uniforms.frag.iplr");
100  ASSERT_TRUE(fixture);
101  ASSERT_GT(fixture->GetSize(), 0u);
102  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
103  ABSL_ASSERT_OK(stages);
104  auto stage = stages.value()[GetRuntimeStageBackend()];
105 
106  ASSERT_TRUE(stage);
107  switch (GetBackend()) {
109  [[fallthrough]];
111  [[fallthrough]];
113  [[fallthrough]];
115  ASSERT_EQ(stage->GetUniforms().size(), 14u);
116  {
117  // uFloat
118  auto uni = stage->GetUniform("uFloat");
119  ASSERT_NE(uni, nullptr);
120  EXPECT_EQ(uni->dimensions.rows, 1u);
121  EXPECT_EQ(uni->dimensions.cols, 1u);
122  EXPECT_EQ(uni->location, 0u);
123  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
124  EXPECT_TRUE(uni->padding_layout.empty());
125  }
126  {
127  // uVec2
128  auto uni = stage->GetUniform("uVec2");
129  ASSERT_NE(uni, nullptr);
130  EXPECT_EQ(uni->dimensions.rows, 2u);
131  EXPECT_EQ(uni->dimensions.cols, 1u);
132  EXPECT_EQ(uni->location, 1u);
133  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
134  EXPECT_TRUE(uni->padding_layout.empty());
135  }
136  {
137  // uVec3
138  auto uni = stage->GetUniform("uVec3");
139  ASSERT_NE(uni, nullptr);
140  EXPECT_EQ(uni->dimensions.rows, 3u);
141  EXPECT_EQ(uni->dimensions.cols, 1u);
142  EXPECT_EQ(uni->location, 2u);
143  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
144  auto padding = uni->padding_layout;
145  if (GetBackend() == PlaygroundBackend::kMetal ||
146  GetBackend() == PlaygroundBackend::kMetalSDF) {
147  EXPECT_EQ(padding.size(), 4u);
148  EXPECT_EQ(padding[0], RuntimePaddingType::kFloat);
149  EXPECT_EQ(padding[1], RuntimePaddingType::kFloat);
150  EXPECT_EQ(padding[2], RuntimePaddingType::kFloat);
151  EXPECT_EQ(padding[3], RuntimePaddingType::kPadding);
152  } else {
153  EXPECT_TRUE(padding.empty());
154  }
155  }
156  {
157  // uVec4
158  auto uni = stage->GetUniform("uVec4");
159  ASSERT_NE(uni, nullptr);
160  EXPECT_EQ(uni->dimensions.rows, 4u);
161  EXPECT_EQ(uni->dimensions.cols, 1u);
162  EXPECT_EQ(uni->location, 3u);
163  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
164  EXPECT_TRUE(uni->padding_layout.empty());
165  }
166  {
167  // uMat2
168  auto uni = stage->GetUniform("uMat2");
169  ASSERT_NE(uni, nullptr);
170  EXPECT_EQ(uni->dimensions.rows, 2u);
171  EXPECT_EQ(uni->dimensions.cols, 2u);
172  EXPECT_EQ(uni->location, 4u);
173  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
174  EXPECT_TRUE(uni->padding_layout.empty());
175  }
176  {
177  // uMat3
178  auto uni = stage->GetUniform("uMat3");
179  ASSERT_NE(uni, nullptr);
180  EXPECT_EQ(uni->dimensions.rows, 3u);
181  EXPECT_EQ(uni->dimensions.cols, 3u);
182  EXPECT_EQ(uni->location, 5u);
183  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
184  }
185  {
186  // uMat4
187  auto uni = stage->GetUniform("uMat4");
188  ASSERT_NE(uni, nullptr);
189  EXPECT_EQ(uni->dimensions.rows, 4u);
190  EXPECT_EQ(uni->dimensions.cols, 4u);
191  EXPECT_EQ(uni->location, 6u);
192  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
193  EXPECT_TRUE(uni->padding_layout.empty());
194  }
195  {
196  // uFloatArray
197  auto uni = stage->GetUniform("uFloatArray");
198  ASSERT_NE(uni, nullptr);
199  EXPECT_EQ(uni->dimensions.rows, 1u);
200  EXPECT_EQ(uni->dimensions.cols, 1u);
201  EXPECT_EQ(uni->location, 7u);
202  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
203  EXPECT_TRUE(uni->padding_layout.empty());
204  }
205  {
206  auto uni = stage->GetUniform("uVec2Array");
207  ASSERT_NE(uni, nullptr);
208  EXPECT_EQ(uni->dimensions.rows, 2u);
209  EXPECT_EQ(uni->dimensions.cols, 1u);
210  EXPECT_EQ(uni->location, 9u);
211  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
212  EXPECT_TRUE(uni->padding_layout.empty());
213  }
214  {
215  // uVec3Array
216  auto uni = stage->GetUniform("uVec3Array");
217  ASSERT_NE(uni, nullptr);
218  EXPECT_EQ(uni->dimensions.rows, 3u);
219  EXPECT_EQ(uni->dimensions.cols, 1u);
220  EXPECT_EQ(uni->location, 11u);
221  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
222  }
223  {
224  // uVec4Array
225  auto uni = stage->GetUniform("uVec4Array");
226  ASSERT_NE(uni, nullptr);
227  EXPECT_EQ(uni->dimensions.rows, 4u);
228  EXPECT_EQ(uni->dimensions.cols, 1u);
229  EXPECT_EQ(uni->location, 13u);
230  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
231  EXPECT_TRUE(uni->padding_layout.empty());
232  }
233  {
234  // uMat2Array
235  auto uni = stage->GetUniform("uMat2Array");
236  ASSERT_NE(uni, nullptr);
237  EXPECT_EQ(uni->dimensions.rows, 2u);
238  EXPECT_EQ(uni->dimensions.cols, 2u);
239  EXPECT_EQ(uni->location, 15u);
240  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
241  EXPECT_TRUE(uni->padding_layout.empty());
242  }
243  {
244  // uMat3Array
245  auto uni = stage->GetUniform("uMat3Array");
246  ASSERT_NE(uni, nullptr);
247  EXPECT_EQ(uni->dimensions.rows, 3u);
248  EXPECT_EQ(uni->dimensions.cols, 3u);
249  EXPECT_EQ(uni->location, 17u);
250  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
251  }
252  {
253  // uMat4Array
254  auto uni = stage->GetUniform("uMat4Array");
255  ASSERT_NE(uni, nullptr);
256  EXPECT_EQ(uni->dimensions.rows, 4u);
257  EXPECT_EQ(uni->dimensions.cols, 4u);
258  EXPECT_EQ(uni->location, 19u);
259  EXPECT_EQ(uni->type, RuntimeUniformType::kFloat);
260  EXPECT_TRUE(uni->padding_layout.empty());
261  }
262  break;
263  }
265  EXPECT_EQ(stage->GetUniforms().size(), 1u);
266  const RuntimeUniformDescription* uni =
267  stage->GetUniform(RuntimeStage::kVulkanUBOName);
268  ASSERT_TRUE(uni);
269  EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
270  EXPECT_EQ(uni->struct_float_count, 26u);
271 
272  EXPECT_EQ(uni->GetGPUSize(), 640u);
273  std::vector<RuntimePaddingType> layout(uni->GetGPUSize() / sizeof(float),
275  // uFloat and uVec2 are packed into a vec4 with 1 byte of padding between.
276  layout[1] = RuntimePaddingType::kPadding;
277  // uVec3 is packed as a vec4 with 1 byte of padding.
278  layout[7] = RuntimePaddingType::kPadding;
279  // uMat2 is packed as two vec4s, with the last 2 bytes of each being
280  // padding.
281  layout[14] = RuntimePaddingType::kPadding;
282  layout[15] = RuntimePaddingType::kPadding;
283  layout[18] = RuntimePaddingType::kPadding;
284  layout[19] = RuntimePaddingType::kPadding;
285  // uMat3 is packed as 3 vec4s, with the last byte of each being padding
286  layout[23] = RuntimePaddingType::kPadding;
287  layout[27] = RuntimePaddingType::kPadding;
288  layout[31] = RuntimePaddingType::kPadding;
289  // uFloatArray is packed as 2 vec4s, with the last 3 bytes of each
290  // being padding.
291  layout[49] = RuntimePaddingType::kPadding;
292  layout[50] = RuntimePaddingType::kPadding;
293  layout[51] = RuntimePaddingType::kPadding;
294  layout[53] = RuntimePaddingType::kPadding;
295  layout[54] = RuntimePaddingType::kPadding;
296  layout[55] = RuntimePaddingType::kPadding;
297  // uVec2Array is packed as 2 vec4s, with 2 bytes of padding at the end of
298  // each.
299  layout[58] = RuntimePaddingType::kPadding;
300  layout[59] = RuntimePaddingType::kPadding;
301  layout[62] = RuntimePaddingType::kPadding;
302  layout[63] = RuntimePaddingType::kPadding;
303  // uVec3Array is packed as 2 vec4s, with the last byte of each as padding.
304  layout[67] = RuntimePaddingType::kPadding;
305  layout[71] = RuntimePaddingType::kPadding;
306  // uVec4Array has no padding.
307  // uMat2Array[2] is packed as 4 vec4s, With the last 2 bytes of each being
308  // padding.
309  layout[82] = RuntimePaddingType::kPadding;
310  layout[83] = RuntimePaddingType::kPadding;
311  layout[86] = RuntimePaddingType::kPadding;
312  layout[87] = RuntimePaddingType::kPadding;
313  layout[90] = RuntimePaddingType::kPadding;
314  layout[91] = RuntimePaddingType::kPadding;
315  layout[94] = RuntimePaddingType::kPadding;
316  layout[95] = RuntimePaddingType::kPadding;
317  // uMat3Array[2] is packed as 6 vec4s, with the last byte of each being
318  // padding.
319  layout[99] = RuntimePaddingType::kPadding;
320  layout[103] = RuntimePaddingType::kPadding;
321  layout[107] = RuntimePaddingType::kPadding;
322  layout[111] = RuntimePaddingType::kPadding;
323  layout[115] = RuntimePaddingType::kPadding;
324  layout[119] = RuntimePaddingType::kPadding;
325  // uMat4Array[2] is packed as 8 vec4s with no padding.
326  layout[152] = RuntimePaddingType::kPadding;
327  layout[153] = RuntimePaddingType::kPadding;
328  layout[154] = RuntimePaddingType::kPadding;
329  layout[155] = RuntimePaddingType::kPadding;
330  layout[156] = RuntimePaddingType::kPadding;
331  layout[157] = RuntimePaddingType::kPadding;
332  layout[158] = RuntimePaddingType::kPadding;
333  layout[159] = RuntimePaddingType::kPadding;
334 
335  EXPECT_THAT(uni->padding_layout, ::testing::ElementsAreArray(layout));
336 
337  std::vector<std::pair<std::string, unsigned int>> expected_uniforms = {
338  {"uFloat", 4}, {"uVec2", 8}, {"uVec3", 12},
339  {"uVec4", 16}, {"uMat2", 16}, {"uMat3", 36},
340  {"uMat4", 64}, {"uFloatArray", 8}, {"uVec2Array", 16},
341  {"uVec3Array", 24}, {"uVec4Array", 32}, {"uMat2Array", 32},
342  {"uMat3Array", 72}, {"uMat4Array", 128}};
343 
344  ASSERT_EQ(uni->struct_fields.size(), expected_uniforms.size());
345 
346  for (size_t i = 0; i < expected_uniforms.size(); ++i) {
347  const auto& element = uni->struct_fields[i];
348  const auto& expected = expected_uniforms[i];
349 
350  EXPECT_EQ(element.name, expected.first) << "index: " << i;
351  EXPECT_EQ(element.byte_size, expected.second) << "index: " << i;
352  }
353  break;
354  }
355  }
356 }
357 
358 TEST_P(RuntimeStageTest, CanReadUniformsSamplerBeforeUBO) {
359  if (GetBackend() != PlaygroundBackend::kVulkan) {
360  GTEST_SKIP() << "Test only relevant for Vulkan";
361  }
362  const std::shared_ptr<fml::Mapping> fixture =
363  flutter::testing::OpenFixtureAsMapping(
364  "uniforms_and_sampler_1.frag.iplr");
365  ASSERT_TRUE(fixture);
366  ASSERT_GT(fixture->GetSize(), 0u);
367  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
368  ABSL_ASSERT_OK(stages);
369  auto stage = stages.value()[GetRuntimeStageBackend()];
370 
371  EXPECT_EQ(stage->GetUniforms().size(), 2u);
372  auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
373  ASSERT_TRUE(uni);
374  // Struct must be offset at 65.
375  EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
376  EXPECT_EQ(uni->binding, 65u);
377  // Sampler should be offset at 64 but due to current bug
378  // has offset of 0, the correct offset is computed at runtime.
379  auto sampler_uniform = stage->GetUniform("u_texture");
380  EXPECT_EQ(sampler_uniform->type, RuntimeUniformType::kSampledImage);
381  EXPECT_EQ(sampler_uniform->binding, 64u);
382 }
383 
384 TEST_P(RuntimeStageTest, CanReadUniformsSamplerAfterUBO) {
385  if (GetBackend() != PlaygroundBackend::kVulkan) {
386  GTEST_SKIP() << "Test only relevant for Vulkan";
387  }
388  const std::shared_ptr<fml::Mapping> fixture =
389  flutter::testing::OpenFixtureAsMapping(
390  "uniforms_and_sampler_2.frag.iplr");
391  ASSERT_TRUE(fixture);
392  ASSERT_GT(fixture->GetSize(), 0u);
393  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
394  ABSL_ASSERT_OK(stages);
395  auto stage = stages.value()[GetRuntimeStageBackend()];
396 
397  EXPECT_EQ(stage->GetUniforms().size(), 2u);
398  auto uni = stage->GetUniform(RuntimeStage::kVulkanUBOName);
399  ASSERT_TRUE(uni);
400  // Struct must be offset at 45.
401  EXPECT_EQ(uni->type, RuntimeUniformType::kStruct);
402  EXPECT_EQ(uni->binding, 64u);
403  // Sampler should be offset at 64 but due to current bug
404  // has offset of 0, the correct offset is computed at runtime.
405  auto sampler_uniform = stage->GetUniform("u_texture");
406  EXPECT_EQ(sampler_uniform->type, RuntimeUniformType::kSampledImage);
407  EXPECT_EQ(sampler_uniform->binding, 65u);
408 }
409 
410 TEST_P(RuntimeStageTest, CanRegisterStage) {
411  const std::shared_ptr<fml::Mapping> fixture =
412  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
413  ASSERT_TRUE(fixture);
414  ASSERT_GT(fixture->GetSize(), 0u);
415  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
416  ABSL_ASSERT_OK(stages);
417  auto stage = stages.value()[GetRuntimeStageBackend()];
418  ASSERT_TRUE(stage);
419  std::promise<bool> registration;
420  auto future = registration.get_future();
421  auto library = GetContext()->GetShaderLibrary();
422  library->RegisterFunction(
423  stage->GetEntrypoint(), //
424  ToShaderStage(stage->GetShaderStage()), //
425  stage->GetCodeMapping(), //
426  fml::MakeCopyable([reg = std::move(registration)](bool result) mutable {
427  reg.set_value(result);
428  }));
429  ASSERT_TRUE(future.get());
430  {
431  auto function =
432  library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
433  ASSERT_NE(function, nullptr);
434  }
435 
436  // Check if unregistering works.
437 
438  library->UnregisterFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
439  {
440  auto function =
441  library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment);
442  ASSERT_EQ(function, nullptr);
443  }
444 }
445 
446 TEST_P(RuntimeStageTest, CanCreatePipelineFromRuntimeStage) {
447  auto stages_result = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr");
448  ABSL_ASSERT_OK(stages_result);
449  auto stage = stages_result.value()[GetRuntimeStageBackend()];
450 
451  ASSERT_TRUE(stage);
452  ASSERT_NE(stage, nullptr);
453  ASSERT_TRUE(RegisterStage(*stage));
454  auto library = GetContext()->GetShaderLibrary();
455  using VS = RuntimeEffectVertexShader;
456  PipelineDescriptor desc;
457  desc.SetLabel("Runtime Stage InkSparkle");
458  desc.AddStageEntrypoint(
459  library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex));
460  desc.AddStageEntrypoint(
461  library->GetFunction(stage->GetEntrypoint(), ShaderStage::kFragment));
462  auto vertex_descriptor = std::make_shared<VertexDescriptor>();
463  vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs,
464  VS::kInterleavedBufferLayout);
465 
466  std::array<DescriptorSetLayout, 2> descriptor_set_layouts = {
467  VS::kDescriptorSetLayouts[0],
469  .binding = 64u,
470  .descriptor_type = DescriptorType::kUniformBuffer,
471  .shader_stage = ShaderStage::kFragment,
472  },
473  };
474  vertex_descriptor->RegisterDescriptorSetLayouts(descriptor_set_layouts);
475 
476  desc.SetVertexDescriptor(std::move(vertex_descriptor));
478  color0.format = GetContext()->GetCapabilities()->GetDefaultColorFormat();
481  desc.SetColorAttachmentDescriptor(0u, color0);
482  desc.SetStencilAttachmentDescriptors(stencil0);
483  const auto stencil_fmt =
484  GetContext()->GetCapabilities()->GetDefaultStencilFormat();
485  desc.SetStencilPixelFormat(stencil_fmt);
486  auto pipeline = GetContext()->GetPipelineLibrary()->GetPipeline(desc).Get();
487  ASSERT_NE(pipeline, nullptr);
488 }
489 
490 TEST_P(RuntimeStageTest, ContainsExpectedShaderTypes) {
491  auto stages_result = OpenAssetAsRuntimeStage("ink_sparkle.frag.iplr");
492  ABSL_ASSERT_OK(stages_result);
493  auto stages = stages_result.value();
494  EXPECT_TRUE(stages[RuntimeStageBackend::kSkSL]);
495  EXPECT_TRUE(stages[RuntimeStageBackend::kOpenGLES]);
496  EXPECT_TRUE(stages[RuntimeStageBackend::kMetal]);
497  EXPECT_TRUE(stages[RuntimeStageBackend::kVulkan]);
498 }
499 
500 TEST_P(RuntimeStageTest, ContainsExpectedShaderTypesNoSksl) {
501  auto stages_result =
502  OpenAssetAsRuntimeStage("runtime_stage_simple_no_sksl.frag.iplr");
503  ABSL_ASSERT_OK(stages_result);
504  auto stages = stages_result.value();
505  EXPECT_FALSE(stages[RuntimeStageBackend::kSkSL]);
506  EXPECT_TRUE(stages[RuntimeStageBackend::kOpenGLES]);
507  EXPECT_TRUE(stages[RuntimeStageBackend::kMetal]);
508  EXPECT_TRUE(stages[RuntimeStageBackend::kVulkan]);
509 }
510 
511 TEST(ShaderKeyTest, MakeUserScopedNameProducesScopedString) {
513  "assets/foo.frag", "main"),
514  "re:assets/foo.frag:main");
515  EXPECT_EQ(
518  "packages/pkg_a/assets/shaders.shaderbundle",
519  "solid_fill_fragment_main"),
520  "fg:packages/pkg_a/assets/shaders.shaderbundle:solid_fill_fragment_main");
521 }
522 
523 TEST(ShaderKeyTest, MakeUserScopedNameContainsColonSeparator) {
524  // The colon separator is what makes user-scoped names unspoofable from
525  // engine-internal entrypoints, since impellerc-generated entrypoints are
526  // valid identifiers and cannot contain ':'.
527  std::string scoped = ShaderKey::MakeUserScopedName(
528  ShaderKey::kScopeRuntimeEffect, "asset", "entry");
529  EXPECT_NE(scoped.find(':'), std::string::npos);
530 }
531 
532 TEST(ShaderKeyTest, MakeUserScopedNameDifferentLibraryIdsDoNotCollide) {
533  // Two user shaders that share an entrypoint name but come from different
534  // logical sources must produce different registry keys, so they cannot
535  // overwrite each other in the shared shader library.
536  std::string a = ShaderKey::MakeUserScopedName(
537  ShaderKey::kScopeFlutterGPU, "packages/pkg_a/bundle", "main");
538  std::string b = ShaderKey::MakeUserScopedName(
539  ShaderKey::kScopeFlutterGPU, "packages/pkg_b/bundle", "main");
540  EXPECT_NE(a, b);
541 }
542 
543 TEST(ShaderKeyTest, MakeUserScopedNameDifferentScopesDoNotCollide) {
544  // A FragmentProgram and a Flutter GPU shader can independently share an
545  // asset path and an entrypoint and must still produce different keys.
546  std::string runtime_effect = ShaderKey::MakeUserScopedName(
547  ShaderKey::kScopeRuntimeEffect, "asset", "main");
548  std::string flutter_gpu = ShaderKey::MakeUserScopedName(
549  ShaderKey::kScopeFlutterGPU, "asset", "main");
550  EXPECT_NE(runtime_effect, flutter_gpu);
551 }
552 
553 TEST(ShaderKeyTest, MakeUserScopedNameHandlesLongInputs) {
554  // The scoped name is used solely as a `std::string` key in the per-process
555  // shader library registry; there is no fixed length limit. Confirm that
556  // long inputs (e.g. deeply-nested package asset paths) round-trip through
557  // the builder without truncation.
558  const std::string long_library_id(4096, 'a');
559  const std::string long_entrypoint(2048, 'b');
560  std::string scoped = ShaderKey::MakeUserScopedName(
561  ShaderKey::kScopeFlutterGPU, long_library_id, long_entrypoint);
562  EXPECT_EQ(scoped.size(), ShaderKey::kScopeFlutterGPU.size() + 1 +
563  long_library_id.size() + 1 +
564  long_entrypoint.size());
565  EXPECT_EQ(scoped.substr(0, 3), "fg:");
566  EXPECT_EQ(scoped.substr(3, long_library_id.size()), long_library_id);
567  EXPECT_EQ(scoped.substr(3 + long_library_id.size(), 1), ":");
568  EXPECT_EQ(scoped.substr(3 + long_library_id.size() + 1), long_entrypoint);
569 }
570 
571 TEST_P(RuntimeStageTest, RuntimeStageHasUniqueLibraryIdByDefault) {
572  // Two stages decoded independently must get distinct fallback library ids
573  // so that programmatically-constructed stages cannot collide with each
574  // other in the shared shader library.
575  const std::shared_ptr<fml::Mapping> fixture =
576  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
577  ASSERT_TRUE(fixture);
578  auto stages_a = RuntimeStage::DecodeRuntimeStages(fixture);
579  ABSL_ASSERT_OK(stages_a);
580  auto stages_b = RuntimeStage::DecodeRuntimeStages(fixture);
581  ABSL_ASSERT_OK(stages_b);
582  auto stage_a = stages_a.value()[GetRuntimeStageBackend()];
583  auto stage_b = stages_b.value()[GetRuntimeStageBackend()];
584  ASSERT_TRUE(stage_a);
585  ASSERT_TRUE(stage_b);
586  EXPECT_FALSE(stage_a->GetLibraryId().empty());
587  EXPECT_FALSE(stage_b->GetLibraryId().empty());
588  EXPECT_NE(stage_a->GetLibraryId(), stage_b->GetLibraryId());
589 }
590 
591 TEST_P(RuntimeStageTest, RuntimeStageLibraryIdCanBeOverridden) {
592  // FragmentProgram::initFromAsset overrides the fallback id with the asset
593  // path so that hot reload of the same asset evicts and replaces the same
594  // registry slot rather than leaking a new one.
595  const std::shared_ptr<fml::Mapping> fixture =
596  flutter::testing::OpenFixtureAsMapping("ink_sparkle.frag.iplr");
597  ASSERT_TRUE(fixture);
598  auto stages = RuntimeStage::DecodeRuntimeStages(fixture);
599  ABSL_ASSERT_OK(stages);
600  auto stage = stages.value()[GetRuntimeStageBackend()];
601  ASSERT_TRUE(stage);
602  stage->SetLibraryId("packages/my_pkg/assets/shader.frag");
603  EXPECT_EQ(stage->GetLibraryId(), "packages/my_pkg/assets/shader.frag");
604 }
605 
606 } // namespace testing
607 } // 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)
Vector2 padding
The halo padding in source space.
static std::optional< RuntimeStageBackend > GetRuntimeStageBackend(TargetPlatform target_platform)
Definition: reflector.cc:347
TEST(AllocationSizeTest, CanCreateTypedAllocations)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
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:109
LinePipeline::VertexShader VS
std::shared_ptr< PipelineGLES > pipeline
Describe the color attachment that will be used with this pipeline.
Definition: formats.h:770
size_t GetGPUSize() const
Computes the total number of bytes that this uniform requires for representation in the GPU.
std::vector< StructField > struct_fields
Definition: runtime_types.h:64
std::vector< RuntimePaddingType > padding_layout
Definition: runtime_types.h:61
static constexpr std::string_view kScopeFlutterGPU
Scope tag for Flutter GPU user shader bundles.
Definition: shader_key.h:59
static std::string MakeUserScopedName(std::string_view scope, std::string_view library_id, std::string_view entrypoint)
Definition: shader_key.cc:19
static constexpr std::string_view kScopeRuntimeEffect
Definition: shader_key.h:56