Flutter Impeller
shader_bundle_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 "gtest/gtest.h"
7 
8 #include "flutter/testing/testing.h"
12 #include "impeller/shader_bundle/shader_bundle_flatbuffers.h"
13 
14 namespace impeller {
15 namespace compiler {
16 namespace testing {
17 
18 const std::string kUnlitFragmentBundleConfig =
19  "\"UnlitFragment\": {\"type\": \"fragment\", \"file\": "
20  "\"shaders/flutter_gpu_unlit.frag\"}";
21 const std::string kUnlitVertexBundleConfig =
22  "\"UnlitVertex\": {\"type\": \"vertex\", \"file\": "
23  "\"shaders/flutter_gpu_unlit.vert\"}";
24 
25 TEST(ShaderBundleTest, ParseShaderBundleConfigFailsForInvalidJSON) {
26  std::string bundle = "";
27  std::stringstream error;
28  auto result = ParseShaderBundleConfig(bundle, error);
29  ASSERT_FALSE(result.has_value());
30  ASSERT_STREQ(error.str().c_str(),
31  "The shader bundle is not a valid JSON object.\n");
32 }
33 
34 TEST(ShaderBundleTest, ParseShaderBundleConfigFailsWhenEntryNotObject) {
35  std::string bundle = "{\"UnlitVertex\": []}";
36  std::stringstream error;
37  auto result = ParseShaderBundleConfig(bundle, error);
38  ASSERT_FALSE(result.has_value());
39  ASSERT_STREQ(
40  error.str().c_str(),
41  "Invalid shader entry \"UnlitVertex\": Entry is not a JSON object.\n");
42 }
43 
44 TEST(ShaderBundleTest, ParseShaderBundleConfigFailsWhenMissingFile) {
45  std::string bundle = "{\"UnlitVertex\": {\"type\": \"vertex\"}}";
46  std::stringstream error;
47  auto result = ParseShaderBundleConfig(bundle, error);
48  ASSERT_FALSE(result.has_value());
49  ASSERT_STREQ(error.str().c_str(),
50  "Invalid shader entry \"UnlitVertex\": Missing required "
51  "\"file\" field.\n");
52 }
53 
54 TEST(ShaderBundleTest, ParseShaderBundleConfigFailsWhenMissingType) {
55  std::string bundle =
56  "{\"UnlitVertex\": {\"file\": \"shaders/flutter_gpu_unlit.vert\"}}";
57  std::stringstream error;
58  auto result = ParseShaderBundleConfig(bundle, error);
59  ASSERT_FALSE(result.has_value());
60  ASSERT_STREQ(error.str().c_str(),
61  "Invalid shader entry \"UnlitVertex\": Missing required "
62  "\"type\" field.\n");
63 }
64 
65 TEST(ShaderBundleTest, ParseShaderBundleConfigFailsForInvalidType) {
66  std::string bundle =
67  "{\"UnlitVertex\": {\"type\": \"invalid\", \"file\": "
68  "\"shaders/flutter_gpu_unlit.vert\"}}";
69  std::stringstream error;
70  auto result = ParseShaderBundleConfig(bundle, error);
71  ASSERT_FALSE(result.has_value());
72  ASSERT_STREQ(error.str().c_str(),
73  "Invalid shader entry \"UnlitVertex\": Shader type "
74  "\"invalid\" is unknown.\n");
75 }
76 
77 TEST(ShaderBundleTest, ParseShaderBundleConfigFailsForInvalidLanguage) {
78  std::string bundle =
79  "{\"UnlitVertex\": {\"type\": \"vertex\", \"language\": \"invalid\", "
80  "\"file\": \"shaders/flutter_gpu_unlit.vert\"}}";
81  std::stringstream error;
82  auto result = ParseShaderBundleConfig(bundle, error);
83  ASSERT_FALSE(result.has_value());
84  ASSERT_STREQ(error.str().c_str(),
85  "Invalid shader entry \"UnlitVertex\": Unknown language type "
86  "\"invalid\".\n");
87 }
88 
89 TEST(ShaderBundleTest, ParseShaderBundleConfigReturnsExpectedConfig) {
90  std::string bundle =
92  std::stringstream error;
93  auto result = ParseShaderBundleConfig(bundle, error);
94  ASSERT_TRUE(result.has_value());
95  ASSERT_STREQ(error.str().c_str(), "");
96 
97  // NOLINTBEGIN(bugprone-unchecked-optional-access)
98  auto maybe_vertex = result->find("UnlitVertex");
99  auto maybe_fragment = result->find("UnlitFragment");
100  ASSERT_TRUE(maybe_vertex != result->end());
101  ASSERT_TRUE(maybe_fragment != result->end());
102  auto vertex = maybe_vertex->second;
103  auto fragment = maybe_fragment->second;
104  // NOLINTEND(bugprone-unchecked-optional-access)
105 
106  EXPECT_EQ(vertex.type, SourceType::kVertexShader);
107  EXPECT_EQ(vertex.language, SourceLanguage::kGLSL);
108  EXPECT_STREQ(vertex.entry_point.c_str(), "main");
109  EXPECT_STREQ(vertex.source_file_name.c_str(),
110  "shaders/flutter_gpu_unlit.vert");
111 
112  EXPECT_EQ(fragment.type, SourceType::kFragmentShader);
113  EXPECT_EQ(fragment.language, SourceLanguage::kGLSL);
114  EXPECT_STREQ(fragment.entry_point.c_str(), "main");
115  EXPECT_STREQ(fragment.source_file_name.c_str(),
116  "shaders/flutter_gpu_unlit.frag");
117 }
118 
119 template <typename T>
120 const T* FindByName(const std::vector<std::unique_ptr<T>>& collection,
121  const std::string& name) {
122  const auto maybe = std::find_if(
123  collection.begin(), collection.end(),
124  [&name](const std::unique_ptr<T>& value) { return value->name == name; });
125  if (maybe == collection.end()) {
126  return nullptr;
127  }
128  return maybe->get();
129 }
130 
131 TEST(ShaderBundleTest, GenerateShaderBundleFlatbufferProducesCorrectResult) {
132  std::string fixtures_path = flutter::testing::GetFixturesPath();
133  std::string config =
134  "{\"UnlitFragment\": {\"type\": \"fragment\", \"file\": \"" +
135  fixtures_path +
136  "/flutter_gpu_unlit.frag\"}, \"UnlitVertex\": {\"type\": "
137  "\"vertex\", \"file\": \"" +
138  fixtures_path + "/flutter_gpu_unlit.vert\"}}";
139 
140  SourceOptions options;
143 
144  std::optional<fb::shaderbundle::ShaderBundleT> bundle =
145  GenerateShaderBundleFlatbuffer(config, options);
146  ASSERT_TRUE(bundle.has_value());
147 
148  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
149  const auto& shaders = bundle->shaders;
150 
151  const auto* vertex = FindByName(shaders, "UnlitVertex");
152  const auto* fragment = FindByName(shaders, "UnlitFragment");
153  ASSERT_NE(vertex, nullptr);
154  ASSERT_NE(fragment, nullptr);
155 
156  // --------------------------------------------------------------------------
157  /// Verify vertex shader.
158  ///
159 
160  EXPECT_STREQ(vertex->metal_desktop->entrypoint.c_str(),
161  "flutter_gpu_unlit_vertex_main");
162  EXPECT_EQ(vertex->metal_desktop->stage,
163  fb::shaderbundle::ShaderStage::kVertex);
164 
165  // Inputs.
166  ASSERT_EQ(vertex->metal_desktop->inputs.size(), 1u);
167  const auto& v_in_position = vertex->metal_desktop->inputs[0];
168  EXPECT_STREQ(v_in_position->name.c_str(), "position");
169  EXPECT_EQ(v_in_position->location, 0u);
170  EXPECT_EQ(v_in_position->set, 0u);
171  EXPECT_EQ(v_in_position->binding, 0u);
172  EXPECT_EQ(v_in_position->type, fb::shaderbundle::InputDataType::kFloat);
173  EXPECT_EQ(v_in_position->bit_width, 32u);
174  EXPECT_EQ(v_in_position->vec_size, 2u);
175  EXPECT_EQ(v_in_position->columns, 1u);
176  EXPECT_EQ(v_in_position->offset, 0u);
177 
178  // Uniforms.
179  ASSERT_EQ(vertex->metal_desktop->uniform_structs.size(), 1u);
180  const auto* vert_info =
181  FindByName(vertex->metal_desktop->uniform_structs, "VertInfo");
182  ASSERT_NE(vert_info, nullptr);
183  EXPECT_EQ(vert_info->ext_res_0, 0u);
184  EXPECT_EQ(vert_info->set, 0u);
185  EXPECT_EQ(vert_info->binding, 0u);
186  ASSERT_EQ(vert_info->fields.size(), 2u);
187  const auto& mvp = vert_info->fields[0];
188  EXPECT_STREQ(mvp->name.c_str(), "mvp");
189  EXPECT_EQ(mvp->type, fb::shaderbundle::UniformDataType::kFloat);
190  EXPECT_EQ(mvp->offset_in_bytes, 0u);
191  EXPECT_EQ(mvp->element_size_in_bytes, 64u);
192  EXPECT_EQ(mvp->total_size_in_bytes, 64u);
193  EXPECT_EQ(mvp->array_elements, 0u);
194  EXPECT_EQ(mvp->vec_size, 4u);
195  EXPECT_EQ(mvp->columns, 4u);
196  const auto& color = vert_info->fields[1];
197  EXPECT_STREQ(color->name.c_str(), "color");
198  EXPECT_EQ(color->type, fb::shaderbundle::UniformDataType::kFloat);
199  EXPECT_EQ(color->offset_in_bytes, 64u);
200  EXPECT_EQ(color->element_size_in_bytes, 16u);
201  EXPECT_EQ(color->total_size_in_bytes, 16u);
202  EXPECT_EQ(color->array_elements, 0u);
203  EXPECT_EQ(color->vec_size, 4u);
204  EXPECT_EQ(color->columns, 1u);
205 
206  // --------------------------------------------------------------------------
207  /// Verify fragment shader.
208  ///
209 
210  EXPECT_STREQ(fragment->metal_desktop->entrypoint.c_str(),
211  "flutter_gpu_unlit_fragment_main");
212  EXPECT_EQ(fragment->metal_desktop->stage,
213  fb::shaderbundle::ShaderStage::kFragment);
214 
215  // Inputs (not recorded for fragment shaders).
216  ASSERT_EQ(fragment->metal_desktop->inputs.size(), 0u);
217 
218  // Uniforms.
219  ASSERT_EQ(fragment->metal_desktop->inputs.size(), 0u);
220 }
221 
222 TEST(ShaderBundleTest,
223  GenerateShaderBundleFlatbufferReportsSourceFilesAsDependencies) {
224  std::string fixtures_path = flutter::testing::GetFixturesPath();
225  const std::string fragment_path = fixtures_path + "/flutter_gpu_unlit.frag";
226  const std::string vertex_path = fixtures_path + "/flutter_gpu_unlit.vert";
227  std::string config =
228  "{\"UnlitFragment\": {\"type\": \"fragment\", \"file\":\"" +
229  fragment_path +
230  "\"}, \"UnlitVertex\": {\"type\": \"vertex\", \"file\": \"" +
231  vertex_path + "\"}}";
232 
233  SourceOptions options;
236 
237  std::set<std::string> dependencies;
238  std::optional<fb::shaderbundle::ShaderBundleT> bundle =
239  GenerateShaderBundleFlatbuffer(config, options, &dependencies);
240  ASSERT_TRUE(bundle.has_value());
241 
242  // Every primary source file referenced by the bundle config should
243  // appear in the dependency set, deduplicated across the multiple
244  // target-platform compiles of each shader. The fixtures used here
245  // don't contain `#include` directives, so the dependency set
246  // contains exactly the two source files.
247  EXPECT_NE(dependencies.find(fragment_path), dependencies.end());
248  EXPECT_NE(dependencies.find(vertex_path), dependencies.end());
249 }
250 
251 TEST(ShaderBundleTest,
252  GenerateShaderBundleFlatbufferIgnoresNullDependencyCollector) {
253  // Passing nullptr as the dependency collector is supported and is
254  // the default behaviour for callers that don't need a depfile.
255  std::string fixtures_path = flutter::testing::GetFixturesPath();
256  std::string config =
257  "{\"UnlitFragment\": {\"type\": \"fragment\", \"file\": \"" +
258  fixtures_path +
259  "/flutter_gpu_unlit.frag\"}, \"UnlitVertex\": {\"type\": \"vertex\", "
260  "\"file\": \"" +
261  fixtures_path + "/flutter_gpu_unlit.vert\"}}";
262 
263  SourceOptions options;
266 
267  std::optional<fb::shaderbundle::ShaderBundleT> bundle =
268  GenerateShaderBundleFlatbuffer(config, options, /*out_dependencies=*/
269  nullptr);
270  ASSERT_TRUE(bundle.has_value());
271 }
272 
273 TEST(ShaderBundleTest, DeriveShaderFloatTypeFromDimensions) {
274  // Non-float types always map to nullopt.
275  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kSignedInt, 1, 1), std::nullopt);
277  std::nullopt);
278  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kBoolean, 1, 1), std::nullopt);
279 
280  // Scalar and vector floats (columns == 1).
281  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 1, 1),
283  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 2, 1),
285  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 3, 1),
287  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 4, 1),
289 
290  // Square matrices (vec_size == columns).
291  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 2, 2),
293  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 3, 3),
295  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 4, 4),
297 
298  // Non-square matrices and unsupported shapes return nullopt.
299  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 3, 2), std::nullopt);
300  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 2, 3), std::nullopt);
301  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 5, 1), std::nullopt);
302 
303  // Zero values (legacy bundle defaults) map to nullopt.
304  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 0, 0), std::nullopt);
305  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 0, 1), std::nullopt);
306  EXPECT_EQ(DeriveShaderFloatType(ShaderType::kFloat, 1, 0), std::nullopt);
307 }
308 
309 TEST(ShaderBundleTest, TargetPlatformDefinesMatchEachBackend) {
311  (std::vector<std::string_view>{"IMPELLER_TARGET_METAL",
312  "IMPELLER_TARGET_METAL_IOS"}));
314  (std::vector<std::string_view>{"IMPELLER_TARGET_METAL",
315  "IMPELLER_TARGET_METAL_DESKTOP"}));
317  (std::vector<std::string_view>{"IMPELLER_TARGET_OPENGLES"}));
318  EXPECT_EQ(
320  (std::vector<std::string_view>{"IMPELLER_TARGET_OPENGL"}));
322  (std::vector<std::string_view>{"IMPELLER_TARGET_VULKAN"}));
323 
324  // Runtime stages and SkSL receive their defines elsewhere; the shader bundle
325  // adds none for them.
326  EXPECT_TRUE(
328  .empty());
329  EXPECT_TRUE(
331 }
332 
333 TEST(ShaderBundleTest, InjectsTargetDefinesDuringCompilation) {
334  // `check_gles_definition.frag` contains an invalid token guarded by
335  // `#ifdef IMPELLER_TARGET_OPENGLES`. Bundling it must fail because the
336  // OpenGLES backend now receives that define and compiles the error branch.
337  // Without the injected define the OpenGLES backend would compile cleanly.
338  std::string fixtures_path = flutter::testing::GetFixturesPath();
339  std::string config = "{\"Probe\": {\"type\": \"fragment\", \"file\": \"" +
340  fixtures_path + "/check_gles_definition.frag\"}}";
341 
342  SourceOptions options;
345 
346  std::optional<fb::shaderbundle::ShaderBundleT> bundle =
347  GenerateShaderBundleFlatbuffer(config, options);
348  EXPECT_FALSE(bundle.has_value());
349 }
350 
351 } // namespace testing
352 } // namespace compiler
353 } // namespace impeller
int32_t value
const std::string kUnlitFragmentBundleConfig
const T * FindByName(const std::vector< std::unique_ptr< T >> &collection, const std::string &name)
TEST(CompilerTest, Defines)
std::vector< std::string_view > GetShaderBundleTargetPlatformDefines(TargetPlatform platform)
The platform-discriminating preprocessor defines injected when compiling a bundled shader for platfor...
std::optional< fb::shaderbundle::ShaderBundleT > GenerateShaderBundleFlatbuffer(const std::string &bundle_config_json, const SourceOptions &options, std::set< std::string > *out_dependencies)
Parses the JSON shader bundle configuration and invokes the compiler multiple times to produce a shad...
std::optional< ShaderBundleConfig > ParseShaderBundleConfig(const std::string &bundle_config_json, std::ostream &error_stream)
Parse a shader bundle configuration from a given JSON string.
constexpr std::optional< ShaderFloatType > DeriveShaderFloatType(ShaderType type, size_t vec_size, size_t columns)
Derive the ShaderFloatType from the base ShaderType and the (vec_size, columns) dimensions reported b...
Definition: shader_types.h:92