Flutter Impeller
compiler_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 <cstring>
6 #include "flutter/testing/testing.h"
7 #include "gtest/gtest.h"
13 
14 namespace impeller {
15 namespace compiler {
16 namespace testing {
17 
19  CompilerSuite,
21  ::testing::Values(TargetPlatform::kOpenGLES,
26  [](const ::testing::TestParamInfo<CompilerTest::ParamType>& info) {
27  return TargetPlatformToString(info.param);
28  });
29 
31  CompilerSuite,
33  ::testing::Values(TargetPlatform::kRuntimeStageMetal,
38  [](const ::testing::TestParamInfo<CompilerTest::ParamType>& info) {
39  return TargetPlatformToString(info.param);
40  });
41 
43  CompilerSuite,
45  ::testing::Values(TargetPlatform::kSkSL),
46  [](const ::testing::TestParamInfo<CompilerTest::ParamType>& info) {
47  return TargetPlatformToString(info.param);
48  });
49 
51  CompilerSuite,
53  ::testing::Values(TargetPlatform::kUnknown),
54  [](const ::testing::TestParamInfo<CompilerTest::ParamType>& info) {
55  return TargetPlatformToString(info.param);
56  });
57 
58 TEST(CompilerTest, Defines) {
59  std::shared_ptr<const fml::Mapping> fixture =
60  flutter::testing::OpenFixtureAsMapping("check_gles_definition.frag");
61 
62  SourceOptions options;
65  options.entry_point_name = "main";
67 
68  Reflector::Options reflector_options;
70  Compiler compiler = Compiler(fixture, options, reflector_options);
71 
72  // Should fail as the shader has a compilation error in it.
73  EXPECT_EQ(compiler.GetSPIRVAssembly(), nullptr);
74 
75  // Should succeed as the compilation error is ifdef'd out.
78  Compiler compiler_2 = Compiler(fixture, options, reflector_options);
79  EXPECT_NE(compiler_2.GetSPIRVAssembly(), nullptr);
80 }
81 
82 TEST(CompilerTest, ShaderKindMatchingIsSuccessful) {
83  ASSERT_EQ(SourceTypeFromFileName("hello.vert"), SourceType::kVertexShader);
84  ASSERT_EQ(SourceTypeFromFileName("hello.frag"), SourceType::kFragmentShader);
85  ASSERT_EQ(SourceTypeFromFileName("hello.comp"), SourceType::kComputeShader);
86  ASSERT_EQ(SourceTypeFromFileName("hello.msl"), SourceType::kUnknown);
87  ASSERT_EQ(SourceTypeFromFileName("hello.glsl"), SourceType::kUnknown);
88 }
89 
90 TEST_P(CompilerTest, CanCompile) {
91  ASSERT_TRUE(CanCompileAndReflect("sample.vert"));
92  ASSERT_TRUE(CanCompileAndReflect("sample.vert", SourceType::kVertexShader));
93  ASSERT_TRUE(CanCompileAndReflect("sample.vert", SourceType::kVertexShader,
95 }
96 
97 TEST_P(CompilerTest, CanCompileHLSL) {
98  ASSERT_TRUE(CanCompileAndReflect(
99  "simple.vert.hlsl", SourceType::kVertexShader, SourceLanguage::kHLSL));
100 }
101 
102 TEST_P(CompilerTest, CanCompileHLSLWithMultipleStages) {
103  ASSERT_TRUE(CanCompileAndReflect("multiple_stages.hlsl",
105  SourceLanguage::kHLSL, "VertexShader"));
106  ASSERT_TRUE(CanCompileAndReflect("multiple_stages.hlsl",
108  SourceLanguage::kHLSL, "FragmentShader"));
109 }
110 
111 TEST_P(CompilerTest, CanCompileComputeShader) {
112  if (!TargetPlatformIsMetal(GetParam())) {
113  GTEST_SKIP()
114  << "Only enabled on Metal backends till ES 3.2 support is added.";
115  }
116  ASSERT_TRUE(CanCompileAndReflect("sample.comp"));
117  ASSERT_TRUE(CanCompileAndReflect("sample.comp", SourceType::kComputeShader));
118 }
119 
120 TEST_P(CompilerTest, MustFailDueToExceedingResourcesLimit) {
121  ScopedValidationDisable disable_validation;
122  ASSERT_FALSE(
123  CanCompileAndReflect("resources_limit.vert", SourceType::kVertexShader));
124 }
125 
126 TEST_P(CompilerTest, MustFailDueToMultipleLocationPerStructMember) {
127  ScopedValidationDisable disable_validation;
128  ASSERT_FALSE(CanCompileAndReflect("struct_def_bug.vert"));
129 }
130 
131 TEST_P(CompilerTest, BindingBaseForFragShader) {
132  if (!TargetPlatformIsVulkan(GetParam())) {
133  GTEST_SKIP();
134  }
135 
136  ASSERT_TRUE(CanCompileAndReflect("sample.vert", SourceType::kVertexShader));
137  ASSERT_TRUE(CanCompileAndReflect("sample.frag", SourceType::kFragmentShader));
138 
139  auto get_binding = [&](const char* fixture) -> uint32_t {
140  auto json_fd = GetReflectionJson(fixture);
141  nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping());
142  return shader_json["buffers"][0]["binding"].get<uint32_t>();
143  };
144 
145  auto vert_uniform_binding = get_binding("sample.vert");
146  auto frag_uniform_binding = get_binding("sample.frag");
147 
148  ASSERT_GT(frag_uniform_binding, vert_uniform_binding);
149 }
150 
151 namespace {
152 struct UniformInfo {
153  std::string uniform_name;
154  uint32_t location;
155  std::string type_name;
156  uint32_t columns;
157  uint32_t vec_size;
158 
159  static UniformInfo fromJson(const nlohmann::json& json) {
160  return {
161  .uniform_name = json["name"].get<std::string>(),
162  .location = json["location"].get<uint32_t>(),
163  .type_name = json["type"]["type_name"].get<std::string>(),
164  .columns = json["type"]["columns"].get<uint32_t>(),
165  .vec_size = json["type"]["vec_size"].get<uint32_t>(),
166  };
167  }
168 
169  static UniformInfo Sampler(const std::string& name, uint32_t location) {
170  return UniformInfo{
171  .uniform_name = name,
172  .location = location,
173  .type_name = "ShaderType::kSampledImage",
174  .columns = 1u,
175  .vec_size = 1u,
176  };
177  }
178  static UniformInfo Float(const std::string& name, uint32_t location) {
179  return FloatInfo(name, location, 1u, 1u);
180  }
181  static UniformInfo Vec2(const std::string& name, uint32_t location) {
182  return FloatInfo(name, location, 1u, 2u);
183  }
184  static UniformInfo Vec3(const std::string& name, uint32_t location) {
185  return FloatInfo(name, location, 1u, 3u);
186  }
187  static UniformInfo Vec4(const std::string& name, uint32_t location) {
188  return FloatInfo(name, location, 1u, 4u);
189  }
190  static UniformInfo Mat4(const std::string& name, uint32_t location) {
191  return FloatInfo(name, location, 4u, 4u);
192  }
193 
194  constexpr bool operator==(const UniformInfo& other) const {
195  return (uniform_name == other.uniform_name && //
196  location == other.location && //
197  type_name == other.type_name && //
198  columns == other.columns && //
199  vec_size == other.vec_size);
200  }
201 
202  private:
203  static UniformInfo FloatInfo(const std::string& name,
204  uint32_t location,
205  uint32_t columns,
206  uint32_t vec_size) {
207  return UniformInfo{
208  .uniform_name = name,
209  .location = location,
210  .type_name = "ShaderType::kFloat",
211  .columns = columns,
212  .vec_size = vec_size,
213  };
214  }
215 };
216 
217 inline std::ostream& operator<<(std::ostream& out, const UniformInfo& info) {
218  out << "UniformInfo {" << std::endl
219  << " uniform_name: " << info.uniform_name << std::endl
220  << " location: " << info.location << std::endl
221  << " type_name: " << info.type_name << std::endl
222  << " columns: " << info.columns << std::endl
223  << " vec_size: " << info.vec_size << std::endl
224  << "}";
225  return out;
226 }
227 } // namespace
228 
229 TEST_P(CompilerTestRuntime, UniformsAppearInJson) {
230  if (GetParam() == TargetPlatform::kRuntimeStageVulkan) {
231  // TODO(https://github.com/flutter/flutter/issues/182578): Investigate why
232  // this does not pass.
233  GTEST_SKIP() << "Not supported with Vulkan";
234  }
235 
236  ASSERT_TRUE(CanCompileAndReflect("sample_with_uniforms.frag",
239 
240  auto json_fd = GetReflectionJson("sample_with_uniforms.frag");
241  ASSERT_TRUE(json_fd);
242  nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping());
243  auto sampler_list = shader_json["sampled_images"];
244  auto float_list = shader_json["uniforms"];
245  ASSERT_EQ(sampler_list.size(), 2u);
246  ASSERT_EQ(float_list.size(), 6u);
247 
248  {
249  // clang-format off
250  std::array expected_infos = {
251  UniformInfo::Sampler("uFirstSampler", 1u),
252  UniformInfo::Sampler("uSampler", 7u),
253  };
254  // clang-format on
255  ASSERT_EQ(sampler_list.size(), expected_infos.size());
256  for (size_t i = 0; i < expected_infos.size(); i++) {
257  EXPECT_EQ(UniformInfo::fromJson(sampler_list[i]), expected_infos[i])
258  << "index: " << i;
259  }
260  }
261 
262  {
263  // clang-format off
264  std::array expected_infos = {
265  UniformInfo::Float("uFirstFloat", 0u),
266  UniformInfo::Float("uFloat", 2u),
267  UniformInfo::Vec2("uVec2", 3u),
268  UniformInfo::Vec3("uVec3", 4u),
269  UniformInfo::Vec4("uVec4", 5u),
270  UniformInfo::Mat4("uMat4", 6u),
271  };
272  // clang-format on
273  ASSERT_EQ(float_list.size(), expected_infos.size());
274  for (size_t i = 0; i < expected_infos.size(); i++) {
275  EXPECT_EQ(UniformInfo::fromJson(float_list[i]), expected_infos[i])
276  << "index: " << i;
277  }
278  }
279 }
280 
281 TEST_P(CompilerTestRuntime, PositionedUniformsAppearInJson) {
282  if (GetParam() == TargetPlatform::kRuntimeStageVulkan) {
283  // TODO(https://github.com/flutter/flutter/issues/182578): Investigate why
284  // this does not pass.
285  GTEST_SKIP() << "Not supported with Vulkan";
286  }
287 
288  ASSERT_TRUE(CanCompileAndReflect("sample_with_positioned_uniforms.frag",
291 
292  auto json_fd = GetReflectionJson("sample_with_positioned_uniforms.frag");
293  ASSERT_TRUE(json_fd);
294  nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping());
295  auto sampler_list = shader_json["sampled_images"];
296  auto float_list = shader_json["uniforms"];
297  ASSERT_EQ(sampler_list.size(), 3u);
298  ASSERT_EQ(float_list.size(), 7u);
299 
300  {
301  // clang-format off
302  std::array expected_infos = {
303  UniformInfo::Sampler("uSamplerNotPositioned1", 1u),
304  UniformInfo::Sampler("uSampler", 0u),
305  UniformInfo::Sampler("uSamplerNotPositioned2", 3u),
306  };
307  // clang-format on
308  ASSERT_EQ(sampler_list.size(), expected_infos.size());
309  for (size_t i = 0; i < expected_infos.size(); i++) {
310  EXPECT_EQ(UniformInfo::fromJson(sampler_list[i]), expected_infos[i])
311  << "index: " << i;
312  }
313  }
314 
315  {
316  // clang-format off
317  std::array expected_infos = {
318  UniformInfo::Float("uFloatNotPositioned1", 0u),
319  UniformInfo::Float("uFloat", 6u),
320  UniformInfo::Vec2("uVec2", 5u),
321  UniformInfo::Vec3("uVec3", 3u),
322  UniformInfo::Vec4("uVec4", 2u),
323  UniformInfo::Mat4("uMat4", 1u),
324  UniformInfo::Float("uFloatNotPositioned2", 2u),
325  };
326  // clang-format on
327  ASSERT_EQ(float_list.size(), expected_infos.size());
328  for (size_t i = 0; i < expected_infos.size(); i++) {
329  EXPECT_EQ(UniformInfo::fromJson(float_list[i]), expected_infos[i])
330  << "index: " << i;
331  }
332  }
333 }
334 
335 TEST_P(CompilerTest, UniformsHaveBindingAndSet) {
336  if (GetParam() == TargetPlatform::kSkSL) {
337  GTEST_SKIP() << "Not supported with SkSL";
338  }
339  ASSERT_TRUE(CanCompileAndReflect("sample_with_binding.vert",
341  ASSERT_TRUE(CanCompileAndReflect("sample.frag", SourceType::kFragmentShader));
342 
343  struct binding_and_set {
344  uint32_t binding;
345  uint32_t set;
346  };
347 
348  auto get_binding = [&](const char* fixture) -> binding_and_set {
349  auto json_fd = GetReflectionJson(fixture);
350  nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping());
351  uint32_t binding = shader_json["buffers"][0]["binding"].get<uint32_t>();
352  uint32_t set = shader_json["buffers"][0]["set"].get<uint32_t>();
353  return {binding, set};
354  };
355 
356  auto vert_uniform_binding = get_binding("sample_with_binding.vert");
357  auto frag_uniform_binding = get_binding("sample.frag");
358 
359  ASSERT_EQ(frag_uniform_binding.set, 0u);
360  ASSERT_EQ(vert_uniform_binding.set, 3u);
361  ASSERT_EQ(vert_uniform_binding.binding, 17u);
362 }
363 
364 TEST_P(CompilerTestSkSL, SkSLTextureLookUpOrderOfOperations) {
365  ASSERT_TRUE(
366  CanCompileAndReflect("texture_lookup.frag", SourceType::kFragmentShader));
367 
368  auto shader = GetShaderFile("texture_lookup.frag", GetParam());
369  std::string_view shader_mapping(
370  reinterpret_cast<const char*>(shader->GetMapping()), shader->GetSize());
371 
372  constexpr std::string_view expected =
373  "textureA.eval(textureA_size * ( vec2(1.0) + flutter_FragCoord.xy));";
374 
375  EXPECT_NE(shader_mapping.find(expected), std::string::npos);
376 }
377 
378 TEST_P(CompilerTestSkSL, CanCompileStructs) {
379  ASSERT_TRUE(CanCompileAndReflect("struct_internal.frag",
381 }
382 
383 TEST_P(CompilerTestSkSL, FailsToCompileDueToArrayInitializerWithConstants) {
384  auto expected_err =
385  "There was a compiler error: SkSL does not support array initializers: "
386  "array_initializer_with_constants.frag:6";
387 
388  EXPECT_EXIT(CanCompileAndReflect("array_initializer_with_constants.frag",
390  ::testing::ExitedWithCode(1), expected_err);
391 }
392 
393 TEST_P(CompilerTestSkSL, FailsToCompileDueToArrayInitializerWithVariables) {
394  auto expected_err =
395  "There was a compiler error: SkSL does not support array initializers: "
396  "array_initializer_with_variables.frag:12";
397 
398  EXPECT_EXIT(CanCompileAndReflect("array_initializer_with_variables.frag",
400  ::testing::ExitedWithCode(1), expected_err);
401 }
402 
403 TEST_P(CompilerTestSkSL, FailsToCompileDueToArrayAssignment) {
404  // Does not EXIT because the backend SkSL compiler doesn't detect the invalid
405  // SkSL. Returns false because the Impeller Compiler's post-compile validation
406  // fails.
407  ASSERT_FALSE(CanCompileAndReflect("array_assignment.frag",
409 
410  const Compiler* compiler = GetCompiler();
411  ASSERT_TRUE(compiler);
412  // Truncated error message.
413  ASSERT_STREQ(
414  compiler->GetErrorMessages().c_str(),
415  "\"array_assignment.frag\": \n"
416  "Compiled to invalid SkSL:\n"
417  " // This SkSL shader is autogenerated by spirv-cross.\n"
418  " \n"
419  " float4 flutter_FragCoord;\n"
420  " \n"
421  " vec4 frag_color;\n"
422  " \n"
423  "... (truncated 16 lines)\n"
424  "SkSL Error:\n"
425  " error: 12: initializers are not permitted on arrays (or structs "
426  "containing arrays)\n"
427  " float more_nums[2] = nums;\n"
428  " ^^^^\n"
429  " 1 error\n");
430  // Full error message.
431  ASSERT_STREQ(compiler->GetVerboseErrorMessages().c_str(),
432  "\"array_assignment.frag\": \n"
433  "Compiled to invalid SkSL:\n"
434  "// This SkSL shader is autogenerated by spirv-cross.\n"
435  "\n"
436  "float4 flutter_FragCoord;\n"
437  "\n"
438  "vec4 frag_color;\n"
439  "\n"
440  "void FLT_main()\n"
441  "{\n"
442  " float nums[2];\n"
443  " nums[0] = 1.0;\n"
444  " nums[1] = 0.5;\n"
445  " float more_nums[2] = nums;\n"
446  " frag_color = vec4(nums[0], nums[1], 1.0, 1.0);\n"
447  "}\n"
448  "\n"
449  "half4 main(float2 iFragCoord)\n"
450  "{\n"
451  " flutter_FragCoord = float4(iFragCoord, 0, 0);\n"
452  " FLT_main();\n"
453  " return frag_color;\n"
454  "}\n"
455  "\n"
456  "SkSL Error:\n"
457  "error: 12: initializers are not permitted on arrays (or "
458  "structs containing arrays)\n"
459  " float more_nums[2] = nums;\n"
460  " ^^^^\n"
461  "1 error\n"
462  "\n");
463 }
464 
465 TEST_P(CompilerTestSkSL, CompilesWithValidArrayInitialization) {
466  ASSERT_TRUE(
467  CanCompileAndReflect("array_initialization_without_initializer.frag",
469 }
470 
471 TEST_P(CompilerTestRuntime, Mat2Reflection) {
472  if (GetParam() == TargetPlatform::kSkSL) {
473  GTEST_SKIP() << "Not supported with SkSL";
474  }
475 
476  ASSERT_TRUE(CanCompileAndReflect(
478 
479  std::unique_ptr<fml::FileMapping> json_fd =
480  GetReflectionJson("mat2_test.frag");
481  ASSERT_TRUE(json_fd);
482  nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping());
483  nlohmann::json::value_type buffers = shader_json["buffers"];
484 
485  // uParams should be in buffers.
486  ASSERT_GE(buffers.size(), 1u);
487  nlohmann::json::value_type uParams = buffers[0];
488  EXPECT_EQ(uParams["name"], "Params");
489 
490  nlohmann::json::value_type members = uParams["type"]["members"];
491  // We expect 1 member (for the mat2) which is a Point (vec2) with array
492  // size 2.
493  ASSERT_EQ(members.size(), 1u);
494 
495  nlohmann::json::value_type mat2Member = members[0];
496 
497  // Should be Mat2 (vec2)
498  EXPECT_EQ(mat2Member["type"], "Mat2");
499 
500  // Size 8 bytes (vec2)
501  EXPECT_EQ(mat2Member["size"], 8u);
502 
503  // Byte length should be total array size (stride * count)
504  // Stride 16 * 2 = 32.
505  EXPECT_EQ(mat2Member["byte_length"], 32u);
506 
507  // Array elements should be 2 (columns).
508  EXPECT_EQ(mat2Member["array_elements"], 2u);
509 
510  // Offset 0
511  EXPECT_EQ(mat2Member["offset"], 0u);
512 }
513 
514 TEST_P(CompilerTestUnknownPlatform, MustFailDueToUnknownPlatform) {
515  ASSERT_FALSE(
516  CanCompileAndReflect("sample.frag", SourceType::kFragmentShader));
517 }
518 
519 } // namespace testing
520 } // namespace compiler
521 } // namespace impeller
std::string GetVerboseErrorMessages() const
Definition: compiler.cc:572
std::shared_ptr< fml::Mapping > GetSPIRVAssembly() const
Definition: compiler.cc:550
std::string GetErrorMessages() const
Definition: compiler.cc:568
uint32_t vec_size
std::string type_name
uint32_t columns
std::string uniform_name
uint32_t location
INSTANTIATE_TEST_SUITE_P(CompilerSuite, CompilerTest, ::testing::Values(TargetPlatform::kOpenGLES, TargetPlatform::kOpenGLDesktop, TargetPlatform::kMetalDesktop, TargetPlatform::kMetalIOS, TargetPlatform::kVulkan), [](const ::testing::TestParamInfo< CompilerTest::ParamType > &info) { return TargetPlatformToString(info.param);})
TEST_P(CompilerTest, CanCompile)
TEST(CompilerTest, Defines)
std::string TargetPlatformToString(TargetPlatform platform)
Definition: types.cc:62
SourceType SourceTypeFromFileName(const std::filesystem::path &file_name)
Definition: types.cc:17
bool TargetPlatformIsMetal(TargetPlatform platform)
Definition: types.cc:258
bool TargetPlatformIsVulkan(TargetPlatform platform)
Definition: types.cc:277
std::ostream & operator<<(std::ostream &out, const impeller::Arc &a)
Definition: arc.h:141