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 <algorithm> |
6 | #include <iomanip> |
7 | #include <iostream> |
8 | #include <iterator> |
9 | #include <sstream> |
10 | #include <string> |
11 | |
12 | #include "flutter/fml/native_library.h" |
13 | #include "flutter/fml/paths.h" |
14 | #include "flutter/fml/size.h" |
15 | #include "flutter/shell/version/version.h" |
16 | |
17 | // Include once for the default enum definition. |
18 | #include "flutter/shell/common/switches.h" |
19 | |
20 | #undef SHELL_COMMON_SWITCHES_H_ |
21 | |
22 | struct SwitchDesc { |
23 | flutter::Switch sw; |
24 | const std::string_view flag; |
25 | const char* help; |
26 | }; |
27 | |
28 | #undef DEF_SWITCHES_START |
29 | #undef DEF_SWITCH |
30 | #undef DEF_SWITCHES_END |
31 | |
32 | // clang-format off |
33 | #define DEF_SWITCHES_START static const struct SwitchDesc gSwitchDescs[] = { |
34 | #define DEF_SWITCH(p_swtch, p_flag, p_help) \ |
35 | { flutter::Switch:: p_swtch, p_flag, p_help }, |
36 | #define DEF_SWITCHES_END }; |
37 | // clang-format on |
38 | |
39 | // List of common and safe VM flags to allow to be passed directly to the VM. |
40 | #if FLUTTER_RELEASE |
41 | |
42 | // clang-format off |
43 | static const std::string kAllowedDartFlags[] = { |
44 | "--enable-isolate-groups" , |
45 | "--no-enable-isolate-groups" , |
46 | }; |
47 | // clang-format on |
48 | |
49 | #else |
50 | |
51 | // clang-format off |
52 | static const std::string kAllowedDartFlags[] = { |
53 | "--enable-isolate-groups" , |
54 | "--no-enable-isolate-groups" , |
55 | "--enable_mirrors" , |
56 | "--enable-service-port-fallback" , |
57 | "--max_profile_depth" , |
58 | "--profile_period" , |
59 | "--random_seed" , |
60 | "--sample-buffer-duration" , |
61 | "--trace-kernel" , |
62 | "--trace-reload" , |
63 | "--trace-reload-verbose" , |
64 | "--write-service-info" , |
65 | "--null_assertions" , |
66 | "--strict_null_safety_checks" , |
67 | "--max_subtype_cache_entries" , |
68 | }; |
69 | // clang-format on |
70 | |
71 | #endif // FLUTTER_RELEASE |
72 | |
73 | // Include again for struct definition. |
74 | #include "flutter/shell/common/switches.h" |
75 | |
76 | // Define symbols for the ICU data that is linked into the Flutter library on |
77 | // Android. This is a workaround for crashes seen when doing dynamic lookups |
78 | // of the engine's own symbols on some older versions of Android. |
79 | #if FML_OS_ANDROID |
80 | extern uint8_t _binary_icudtl_dat_start[]; |
81 | extern uint8_t _binary_icudtl_dat_end[]; |
82 | |
83 | static std::unique_ptr<fml::Mapping> GetICUStaticMapping() { |
84 | return std::make_unique<fml::NonOwnedMapping>( |
85 | _binary_icudtl_dat_start, |
86 | _binary_icudtl_dat_end - _binary_icudtl_dat_start); |
87 | } |
88 | #endif |
89 | |
90 | namespace flutter { |
91 | |
92 | void PrintUsage(const std::string& executable_name) { |
93 | std::cerr << std::endl << " " << executable_name << std::endl << std::endl; |
94 | |
95 | std::cerr << "Versions: " << std::endl << std::endl; |
96 | |
97 | std::cerr << "Flutter Engine Version: " << GetFlutterEngineVersion() |
98 | << std::endl; |
99 | std::cerr << "Skia Version: " << GetSkiaVersion() << std::endl; |
100 | |
101 | std::cerr << "Dart Version: " << GetDartVersion() << std::endl << std::endl; |
102 | |
103 | std::cerr << "Available Flags:" << std::endl; |
104 | |
105 | const uint32_t column_width = 80; |
106 | |
107 | const uint32_t flags_count = static_cast<uint32_t>(Switch::Sentinel); |
108 | |
109 | uint32_t max_width = 2; |
110 | for (uint32_t i = 0; i < flags_count; i++) { |
111 | auto desc = gSwitchDescs[i]; |
112 | max_width = std::max<uint32_t>(a: desc.flag.size() + 2, b: max_width); |
113 | } |
114 | |
115 | const uint32_t help_width = column_width - max_width - 3; |
116 | |
117 | std::cerr << std::string(column_width, '-') << std::endl; |
118 | for (uint32_t i = 0; i < flags_count; i++) { |
119 | auto desc = gSwitchDescs[i]; |
120 | |
121 | std::cerr << std::setw(max_width) |
122 | << std::string("--" ) + |
123 | std::string{desc.flag.data(), desc.flag.size()} |
124 | << " : " ; |
125 | |
126 | std::istringstream stream(desc.help); |
127 | int32_t remaining = help_width; |
128 | |
129 | std::string word; |
130 | while (stream >> word && remaining > 0) { |
131 | remaining -= (word.size() + 1); |
132 | if (remaining <= 0) { |
133 | std::cerr << std::endl |
134 | << std::string(max_width, ' ') << " " << word << " " ; |
135 | remaining = help_width; |
136 | } else { |
137 | std::cerr << word << " " ; |
138 | } |
139 | } |
140 | |
141 | std::cerr << std::endl; |
142 | } |
143 | std::cerr << std::string(column_width, '-') << std::endl; |
144 | } |
145 | |
146 | const std::string_view FlagForSwitch(Switch swtch) { |
147 | for (uint32_t i = 0; i < static_cast<uint32_t>(Switch::Sentinel); i++) { |
148 | if (gSwitchDescs[i].sw == swtch) { |
149 | return gSwitchDescs[i].flag; |
150 | } |
151 | } |
152 | return std::string_view(); |
153 | } |
154 | |
155 | static std::vector<std::string> ParseCommaDelimited(const std::string& input) { |
156 | std::istringstream ss(input); |
157 | std::vector<std::string> result; |
158 | std::string token; |
159 | while (std::getline(is&: ss, str&: token, dlm: ',')) { |
160 | result.push_back(x: token); |
161 | } |
162 | return result; |
163 | } |
164 | |
165 | static bool IsAllowedDartVMFlag(const std::string& flag) { |
166 | for (uint32_t i = 0; i < fml::size(array: kAllowedDartFlags); ++i) { |
167 | const std::string& allowed = kAllowedDartFlags[i]; |
168 | // Check that the prefix of the flag matches one of the allowed flags. This |
169 | // is to handle cases where flags take arguments, such as in |
170 | // "--max_profile_depth 1". |
171 | // |
172 | // We don't need to worry about cases like "--safe --sneaky_dangerous" as |
173 | // the VM will discard these as a single unrecognized flag. |
174 | if (flag.length() >= allowed.length() && |
175 | std::equal(first1: allowed.begin(), last1: allowed.end(), first2: flag.begin())) { |
176 | return true; |
177 | } |
178 | } |
179 | return false; |
180 | } |
181 | |
182 | template <typename T> |
183 | static bool GetSwitchValue(const fml::CommandLine& command_line, |
184 | Switch sw, |
185 | T* result) { |
186 | std::string switch_string; |
187 | |
188 | if (!command_line.GetOptionValue(name: FlagForSwitch(swtch: sw), value: &switch_string)) { |
189 | return false; |
190 | } |
191 | |
192 | std::stringstream stream(switch_string); |
193 | T value = 0; |
194 | if (stream >> value) { |
195 | *result = value; |
196 | return true; |
197 | } |
198 | |
199 | return false; |
200 | } |
201 | |
202 | std::unique_ptr<fml::Mapping> GetSymbolMapping( |
203 | const std::string& symbol_prefix, |
204 | const std::string& native_lib_path) { |
205 | const uint8_t* mapping = nullptr; |
206 | intptr_t size; |
207 | |
208 | auto lookup_symbol = [&mapping, &size, symbol_prefix]( |
209 | const fml::RefPtr<fml::NativeLibrary>& library) { |
210 | mapping = library->ResolveSymbol(symbol: (symbol_prefix + "_start" ).c_str()); |
211 | size = reinterpret_cast<intptr_t>( |
212 | library->ResolveSymbol(symbol: (symbol_prefix + "_size" ).c_str())); |
213 | }; |
214 | |
215 | fml::RefPtr<fml::NativeLibrary> library = |
216 | fml::NativeLibrary::CreateForCurrentProcess(); |
217 | lookup_symbol(library); |
218 | |
219 | if (!(mapping && size)) { |
220 | // Symbol lookup for the current process fails on some devices. As a |
221 | // fallback, try doing the lookup based on the path to the Flutter library. |
222 | library = fml::NativeLibrary::Create(path: native_lib_path.c_str()); |
223 | lookup_symbol(library); |
224 | } |
225 | |
226 | FML_CHECK(mapping && size) << "Unable to resolve symbols: " << symbol_prefix; |
227 | return std::make_unique<fml::NonOwnedMapping>(args&: mapping, args&: size); |
228 | } |
229 | |
230 | Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { |
231 | Settings settings = {}; |
232 | |
233 | // Set executable name. |
234 | if (command_line.has_argv0()) { |
235 | settings.executable_name = command_line.argv0(); |
236 | } |
237 | |
238 | // Enable the VM Service |
239 | settings.enable_vm_service = |
240 | !command_line.HasOption(name: FlagForSwitch(swtch: Switch::DisableVMService)) && |
241 | // TODO(bkonyi): remove once flutter_tools no longer uses this option. |
242 | // See https://github.com/dart-lang/sdk/issues/50233 |
243 | !command_line.HasOption(name: FlagForSwitch(swtch: Switch::DisableObservatory)); |
244 | |
245 | // Enable mDNS VM Service Publication |
246 | settings.enable_vm_service_publication = |
247 | !command_line.HasOption( |
248 | name: FlagForSwitch(swtch: Switch::DisableVMServicePublication)) && |
249 | !command_line.HasOption( |
250 | name: FlagForSwitch(swtch: Switch::DisableObservatoryPublication)); |
251 | |
252 | // Set VM Service Host |
253 | if (command_line.HasOption(name: FlagForSwitch(swtch: Switch::DeviceVMServiceHost))) { |
254 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::DeviceVMServiceHost), |
255 | value: &settings.vm_service_host); |
256 | } else if (command_line.HasOption( |
257 | name: FlagForSwitch(swtch: Switch::DeviceObservatoryHost))) { |
258 | // TODO(bkonyi): remove once flutter_tools no longer uses this option. |
259 | // See https://github.com/dart-lang/sdk/issues/50233 |
260 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::DeviceObservatoryHost), |
261 | value: &settings.vm_service_host); |
262 | } |
263 | // Default the VM Service port based on --ipv6 if not set. |
264 | if (settings.vm_service_host.empty()) { |
265 | settings.vm_service_host = |
266 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::IPv6)) ? "::1" |
267 | : "127.0.0.1" ; |
268 | } |
269 | |
270 | // Set VM Service Port |
271 | if (command_line.HasOption(name: FlagForSwitch(swtch: Switch::DeviceVMServicePort))) { |
272 | if (!GetSwitchValue(command_line, sw: Switch::DeviceVMServicePort, |
273 | result: &settings.vm_service_port)) { |
274 | FML_LOG(INFO) |
275 | << "VM Service port specified was malformed. Will default to " |
276 | << settings.vm_service_port; |
277 | } |
278 | } else if (command_line.HasOption( |
279 | name: FlagForSwitch(swtch: Switch::DeviceObservatoryPort))) { |
280 | // TODO(bkonyi): remove once flutter_tools no longer uses this option. |
281 | // See https://github.com/dart-lang/sdk/issues/50233 |
282 | if (!GetSwitchValue(command_line, sw: Switch::DeviceObservatoryPort, |
283 | result: &settings.vm_service_port)) { |
284 | FML_LOG(INFO) |
285 | << "VM Service port specified was malformed. Will default to " |
286 | << settings.vm_service_port; |
287 | } |
288 | } |
289 | |
290 | settings.may_insecurely_connect_to_all_domains = !command_line.HasOption( |
291 | name: FlagForSwitch(swtch: Switch::DisallowInsecureConnections)); |
292 | |
293 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::DomainNetworkPolicy), |
294 | value: &settings.domain_network_policy); |
295 | |
296 | // Disable need for authentication codes for VM service communication, if |
297 | // specified. |
298 | settings.disable_service_auth_codes = |
299 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::DisableServiceAuthCodes)); |
300 | |
301 | // Allow fallback to automatic port selection if binding to a specified port |
302 | // fails. |
303 | settings.enable_service_port_fallback = |
304 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EnableServicePortFallback)); |
305 | |
306 | // Checked mode overrides. |
307 | settings.disable_dart_asserts = |
308 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::DisableDartAsserts)); |
309 | |
310 | settings.start_paused = |
311 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::StartPaused)); |
312 | |
313 | settings.enable_checked_mode = |
314 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EnableCheckedMode)); |
315 | |
316 | settings.enable_dart_profiling = |
317 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EnableDartProfiling)); |
318 | |
319 | settings.enable_software_rendering = |
320 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EnableSoftwareRendering)); |
321 | |
322 | settings.endless_trace_buffer = |
323 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EndlessTraceBuffer)); |
324 | |
325 | settings.trace_startup = |
326 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::TraceStartup)); |
327 | |
328 | settings.enable_serial_gc = |
329 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EnableSerialGC)); |
330 | |
331 | #if !FLUTTER_RELEASE |
332 | settings.trace_skia = true; |
333 | |
334 | if (command_line.HasOption(name: FlagForSwitch(swtch: Switch::TraceSkia))) { |
335 | // If --trace-skia is specified, then log all Skia events. |
336 | settings.trace_skia_allowlist.reset(); |
337 | } else { |
338 | std::string trace_skia_allowlist; |
339 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::TraceSkiaAllowlist), |
340 | value: &trace_skia_allowlist); |
341 | if (trace_skia_allowlist.size()) { |
342 | settings.trace_skia_allowlist = ParseCommaDelimited(input: trace_skia_allowlist); |
343 | } else { |
344 | settings.trace_skia_allowlist = {"skia.shaders" }; |
345 | } |
346 | } |
347 | #endif // !FLUTTER_RELEASE |
348 | |
349 | std::string trace_allowlist; |
350 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::TraceAllowlist), |
351 | value: &trace_allowlist); |
352 | settings.trace_allowlist = ParseCommaDelimited(input: trace_allowlist); |
353 | |
354 | settings.trace_systrace = |
355 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::TraceSystrace)); |
356 | |
357 | settings.skia_deterministic_rendering_on_cpu = |
358 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::SkiaDeterministicRendering)); |
359 | |
360 | settings.verbose_logging = |
361 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::VerboseLogging)); |
362 | |
363 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::FlutterAssetsDir), |
364 | value: &settings.assets_path); |
365 | |
366 | std::vector<std::string_view> aot_shared_library_name = |
367 | command_line.GetOptionValues(name: FlagForSwitch(swtch: Switch::AotSharedLibraryName)); |
368 | |
369 | std::vector<std::string_view> vmservice_shared_library_name = |
370 | command_line.GetOptionValues( |
371 | name: FlagForSwitch(swtch: Switch::AotVMServiceSharedLibraryName)); |
372 | for (auto path : vmservice_shared_library_name) { |
373 | settings.vmservice_snapshot_library_path.emplace_back(args&: path); |
374 | } |
375 | |
376 | std::string snapshot_asset_path; |
377 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::SnapshotAssetPath), |
378 | value: &snapshot_asset_path); |
379 | |
380 | std::string vm_snapshot_data_filename; |
381 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::VmSnapshotData), |
382 | value: &vm_snapshot_data_filename); |
383 | |
384 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::Route), value: &settings.route); |
385 | |
386 | std::string vm_snapshot_instr_filename; |
387 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::VmSnapshotInstructions), |
388 | value: &vm_snapshot_instr_filename); |
389 | |
390 | std::string isolate_snapshot_data_filename; |
391 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::IsolateSnapshotData), |
392 | value: &isolate_snapshot_data_filename); |
393 | |
394 | std::string isolate_snapshot_instr_filename; |
395 | command_line.GetOptionValue( |
396 | name: FlagForSwitch(swtch: Switch::IsolateSnapshotInstructions), |
397 | value: &isolate_snapshot_instr_filename); |
398 | |
399 | if (!aot_shared_library_name.empty()) { |
400 | for (std::string_view name : aot_shared_library_name) { |
401 | settings.application_library_path.emplace_back(args&: name); |
402 | } |
403 | } else if (!snapshot_asset_path.empty()) { |
404 | settings.vm_snapshot_data_path = |
405 | fml::paths::JoinPaths(components: {snapshot_asset_path, vm_snapshot_data_filename}); |
406 | settings.vm_snapshot_instr_path = fml::paths::JoinPaths( |
407 | components: {snapshot_asset_path, vm_snapshot_instr_filename}); |
408 | settings.isolate_snapshot_data_path = fml::paths::JoinPaths( |
409 | components: {snapshot_asset_path, isolate_snapshot_data_filename}); |
410 | settings.isolate_snapshot_instr_path = fml::paths::JoinPaths( |
411 | components: {snapshot_asset_path, isolate_snapshot_instr_filename}); |
412 | } |
413 | |
414 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::CacheDirPath), |
415 | value: &settings.temp_directory_path); |
416 | |
417 | bool leak_vm = "true" == command_line.GetOptionValueWithDefault( |
418 | name: FlagForSwitch(swtch: Switch::LeakVM), default_value: "true" ); |
419 | settings.leak_vm = leak_vm; |
420 | |
421 | if (settings.icu_initialization_required) { |
422 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::ICUDataFilePath), |
423 | value: &settings.icu_data_path); |
424 | if (command_line.HasOption(name: FlagForSwitch(swtch: Switch::ICUSymbolPrefix))) { |
425 | std::string icu_symbol_prefix, native_lib_path; |
426 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::ICUSymbolPrefix), |
427 | value: &icu_symbol_prefix); |
428 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::ICUNativeLibPath), |
429 | value: &native_lib_path); |
430 | |
431 | #if FML_OS_ANDROID |
432 | settings.icu_mapper = GetICUStaticMapping; |
433 | #else |
434 | settings.icu_mapper = [icu_symbol_prefix, native_lib_path] { |
435 | return GetSymbolMapping(symbol_prefix: icu_symbol_prefix, native_lib_path); |
436 | }; |
437 | #endif |
438 | } |
439 | } |
440 | |
441 | settings.use_test_fonts = |
442 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::UseTestFonts)); |
443 | settings.use_asset_fonts = |
444 | !command_line.HasOption(name: FlagForSwitch(swtch: Switch::DisableAssetFonts)); |
445 | |
446 | { |
447 | std::string enable_impeller_value; |
448 | if (command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::EnableImpeller), |
449 | value: &enable_impeller_value)) { |
450 | settings.enable_impeller = |
451 | enable_impeller_value.empty() || "true" == enable_impeller_value; |
452 | } |
453 | } |
454 | |
455 | { |
456 | std::string impeller_backend_value; |
457 | if (command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::ImpellerBackend), |
458 | value: &impeller_backend_value)) { |
459 | if (!impeller_backend_value.empty()) { |
460 | settings.impeller_backend = impeller_backend_value; |
461 | } |
462 | } |
463 | } |
464 | |
465 | settings.enable_vulkan_validation = |
466 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EnableVulkanValidation)); |
467 | |
468 | settings.enable_embedder_api = |
469 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::EnableEmbedderAPI)); |
470 | |
471 | settings.prefetched_default_font_manager = command_line.HasOption( |
472 | name: FlagForSwitch(swtch: Switch::PrefetchedDefaultFontManager)); |
473 | |
474 | std::string all_dart_flags; |
475 | if (command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::DartFlags), |
476 | value: &all_dart_flags)) { |
477 | // Assume that individual flags are comma separated. |
478 | std::vector<std::string> flags = ParseCommaDelimited(input: all_dart_flags); |
479 | for (auto flag : flags) { |
480 | if (!IsAllowedDartVMFlag(flag)) { |
481 | FML_LOG(FATAL) << "Encountered disallowed Dart VM flag: " << flag; |
482 | } |
483 | settings.dart_flags.push_back(x: flag); |
484 | } |
485 | } |
486 | |
487 | #if !FLUTTER_RELEASE |
488 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::LogTag), value: &settings.log_tag); |
489 | #endif |
490 | |
491 | settings.dump_skp_on_shader_compilation = |
492 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::DumpSkpOnShaderCompilation)); |
493 | |
494 | settings.cache_sksl = |
495 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::CacheSkSL)); |
496 | |
497 | settings.purge_persistent_cache = |
498 | command_line.HasOption(name: FlagForSwitch(swtch: Switch::PurgePersistentCache)); |
499 | |
500 | if (command_line.HasOption(name: FlagForSwitch(swtch: Switch::OldGenHeapSize))) { |
501 | std::string old_gen_heap_size; |
502 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::OldGenHeapSize), |
503 | value: &old_gen_heap_size); |
504 | settings.old_gen_heap_size = std::stoi(str: old_gen_heap_size); |
505 | } |
506 | |
507 | if (command_line.HasOption( |
508 | name: FlagForSwitch(swtch: Switch::ResourceCacheMaxBytesThreshold))) { |
509 | std::string resource_cache_max_bytes_threshold; |
510 | command_line.GetOptionValue( |
511 | name: FlagForSwitch(swtch: Switch::ResourceCacheMaxBytesThreshold), |
512 | value: &resource_cache_max_bytes_threshold); |
513 | settings.resource_cache_max_bytes_threshold = |
514 | std::stoi(str: resource_cache_max_bytes_threshold); |
515 | } |
516 | |
517 | if (command_line.HasOption(name: FlagForSwitch(swtch: Switch::MsaaSamples))) { |
518 | std::string msaa_samples; |
519 | command_line.GetOptionValue(name: FlagForSwitch(swtch: Switch::MsaaSamples), |
520 | value: &msaa_samples); |
521 | if (msaa_samples == "0" ) { |
522 | settings.msaa_samples = 0; |
523 | } else if (msaa_samples == "1" ) { |
524 | settings.msaa_samples = 1; |
525 | } else if (msaa_samples == "2" ) { |
526 | settings.msaa_samples = 2; |
527 | } else if (msaa_samples == "4" ) { |
528 | settings.msaa_samples = 4; |
529 | } else if (msaa_samples == "8" ) { |
530 | settings.msaa_samples = 8; |
531 | } else if (msaa_samples == "16" ) { |
532 | settings.msaa_samples = 16; |
533 | } else { |
534 | FML_DLOG(ERROR) << "Invalid value for --msaa-samples: '" << msaa_samples |
535 | << "' (expected 0, 1, 2, 4, 8, or 16)." ; |
536 | } |
537 | } |
538 | |
539 | return settings; |
540 | } |
541 | |
542 | } // namespace flutter |
543 | |