Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 107 additions & 37 deletions dev/devicelab/bin/tasks/hello_world_impeller_linux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,126 @@ Future<TaskResult> run() async {
deviceOperatingSystem = DeviceOperatingSystem.linux;

final Directory appDir = dir(path.join(flutterDirectory.path, 'examples/hello_world'));
final String myApplicationPath = path.join(appDir.path, 'linux', 'runner', 'my_application.cc');
final myApplicationFile = File(myApplicationPath);
Comment on lines +19 to +20
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Declare a variable to store the original content of my_application.cc so we can restore it in the finally block without spawning a git checkout process.

Suggested change
final String myApplicationPath = path.join(appDir.path, 'linux', 'runner', 'my_application.cc');
final myApplicationFile = File(myApplicationPath);
final String myApplicationPath = path.join(appDir.path, 'linux', 'runner', 'my_application.cc');
final myApplicationFile = File(myApplicationPath);
String? originalContent;


const vulkanBackendMessage = 'Using the Impeller rendering backend (VulkanSDF).';
const openGLBackendMessage = 'Using the Impeller rendering backend (OpenGLESSDF).';

var res = TaskResult.success(null);

try {
await inDirectory(appDir, () async {
await flutter('packages', options: <String>['get']);

final Process process = await startFlutter(
'run',
options: <String>['--enable-impeller', '-d', 'linux'],
);

final completer = Completer<void>();
var sawImpellerBackendMessage = false;
const vulkanBackendMessage = 'Using the Impeller rendering backend (VulkanSDF).';
const openGLBackendMessage = 'Using the Impeller rendering backend (OpenGLESSDF).';

final StreamSubscription<String> subscription = process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
print('[STDOUT]: $line');
if (line.contains(vulkanBackendMessage) || line.contains(openGLBackendMessage)) {
sawImpellerBackendMessage = true;
if (!completer.isCompleted) {
completer.complete();
// Step 1: Test using command line flag.
{
final Process process = await startFlutter(
'run',
options: <String>['--enable-impeller', '-d', 'linux'],
);

final completer = Completer<void>();
var sawImpellerBackendMessage = false;

final StreamSubscription<String> subscription = process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
print('[STDOUT 1]: $line');
if (line.contains(vulkanBackendMessage) || line.contains(openGLBackendMessage)) {
sawImpellerBackendMessage = true;
if (!completer.isCompleted) {
completer.complete();
}
}
}
});

await Future.any(<Future<void>>[
completer.future,
Future<void>.delayed(const Duration(minutes: 2)),
]);

process.stdin.writeln('q');
final int exitCode = await process.exitCode;
await subscription.cancel();

if (exitCode != 0) {
res = TaskResult.failure('Flutter process exited with non-zero exit code: $exitCode');
} else if (!sawImpellerBackendMessage) {
res = TaskResult.failure(
'Did not see "$vulkanBackendMessage" or '
'"$openGLBackendMessage" in output',
});

await Future.any(<Future<void>>[
completer.future,
Future<void>.delayed(const Duration(minutes: 2)),
]);

process.stdin.writeln('q');
final int exitCode = await process.exitCode;
await subscription.cancel();

if (exitCode != 0) {
res = TaskResult.failure('Flutter process 1 exited with non-zero exit code: $exitCode');
return;
} else if (!sawImpellerBackendMessage) {
res = TaskResult.failure(
'Did not see "$vulkanBackendMessage" or '
'"$openGLBackendMessage" in output (Step 1)',
);
return;
}
}

// Step 2: Test using project flag.
{
if (!myApplicationFile.existsSync()) {
res = TaskResult.failure('my_application.cc not found at $myApplicationPath');
return;
}

final String originalContent = myApplicationFile.readAsStringSync();
final String modifiedContent = originalContent.replaceFirst(
Comment on lines +82 to +83
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Assign the read content to the originalContent variable so it can be restored in the finally block.

Suggested change
final String originalContent = myApplicationFile.readAsStringSync();
final String modifiedContent = originalContent.replaceFirst(
originalContent = myApplicationFile.readAsStringSync();
final String modifiedContent = originalContent!.replaceFirst(

'g_autoptr(FlDartProject) project = fl_dart_project_new();',
'g_autoptr(FlDartProject) project = fl_dart_project_new();\n fl_dart_project_set_enable_impeller(project, TRUE);',
);
if (modifiedContent == originalContent) {
res = TaskResult.failure('Failed to modify my_application.cc');
return;
}
myApplicationFile.writeAsStringSync(modifiedContent);

// Run 'flutter run' without command-line flag --enable-impeller.
final Process process = await startFlutter('run', options: <String>['-d', 'linux']);

final completer = Completer<void>();
var sawImpellerBackendMessage = false;

final StreamSubscription<String> subscription = process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
print('[STDOUT 2]: $line');
if (line.contains(vulkanBackendMessage) || line.contains(openGLBackendMessage)) {
sawImpellerBackendMessage = true;
if (!completer.isCompleted) {
completer.complete();
}
}
});

await Future.any(<Future<void>>[
completer.future,
Future<void>.delayed(const Duration(minutes: 2)),
]);

process.stdin.writeln('q');
final int exitCode = await process.exitCode;
await subscription.cancel();

if (exitCode != 0) {
res = TaskResult.failure('Flutter process 2 exited with non-zero exit code: $exitCode');
return;
} else if (!sawImpellerBackendMessage) {
res = TaskResult.failure(
'Did not see "$vulkanBackendMessage" or '
'"$openGLBackendMessage" in output (Step 2)',
);
return;
}
}
});
} catch (e) {
res = TaskResult.failure('Test failed with exception: $e');
} finally {
if (myApplicationFile.existsSync()) {
await exec('git', <String>['checkout', myApplicationPath]);
}
}
Comment on lines +135 to 139
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Restore the original content of my_application.cc directly using writeAsStringSync instead of spawning a git checkout process. This is faster, safer, and avoids external process overhead.

Suggested change
} finally {
if (myApplicationFile.existsSync()) {
await exec('git', <String>['checkout', myApplicationPath]);
}
}
} finally {
if (originalContent != null) {
myApplicationFile.writeAsStringSync(originalContent!);
}
}


return res;
Expand Down
15 changes: 15 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_dart_project.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct _FlDartProject {
gchar** dart_entrypoint_args;

FlUIThreadPolicy ui_thread_policy;
gboolean enable_impeller;
};

G_DEFINE_TYPE(FlDartProject, fl_dart_project, G_TYPE_OBJECT)
Expand Down Expand Up @@ -60,6 +61,7 @@ G_MODULE_EXPORT FlDartProject* fl_dart_project_new() {
g_build_filename(executable_dir, "data", "flutter_assets", nullptr);
self->icu_data_path =
g_build_filename(executable_dir, "data", "icudtl.dat", nullptr);
self->enable_impeller = FALSE;

return self;
}
Expand Down Expand Up @@ -130,3 +132,16 @@ FlUIThreadPolicy fl_dart_project_get_ui_thread_policy(FlDartProject* project) {
FL_UI_THREAD_POLICY_DEFAULT);
return project->ui_thread_policy;
}

G_MODULE_EXPORT
void fl_dart_project_set_enable_impeller(FlDartProject* project,
gboolean enable_impeller) {
g_return_if_fail(FL_IS_DART_PROJECT(project));
project->enable_impeller = enable_impeller;
}

G_MODULE_EXPORT
gboolean fl_dart_project_get_enable_impeller(FlDartProject* project) {
g_return_val_if_fail(FL_IS_DART_PROJECT(project), FALSE);
return project->enable_impeller;
}
11 changes: 11 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_dart_project_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,14 @@ TEST(FlDartProjectTest, DartEntrypointArgs) {

EXPECT_EQ(g_strv_length(retrieved_args), 3U);
}

TEST(FlDartProjectTest, EnableImpeller) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
EXPECT_FALSE(fl_dart_project_get_enable_impeller(project));

fl_dart_project_set_enable_impeller(project, TRUE);
EXPECT_TRUE(fl_dart_project_get_enable_impeller(project));

fl_dart_project_set_enable_impeller(project, FALSE);
EXPECT_FALSE(fl_dart_project_get_enable_impeller(project));
}
17 changes: 17 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,19 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
break;
}

gboolean enable_impeller = fl_dart_project_get_enable_impeller(self->project);
gboolean has_enable_impeller = FALSE;
for (const auto& env_switch : flutter::GetSwitchesFromEnvironment()) {
if (env_switch == "--enable-impeller" ||
env_switch == "--enable-impeller=true") {
enable_impeller = TRUE;
has_enable_impeller = TRUE;
} else if (env_switch == "--enable-impeller=false") {
enable_impeller = FALSE;
has_enable_impeller = TRUE;
}
}
Comment on lines +815 to +826
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use standard C++ bool and true/false literals instead of GLib's gboolean and TRUE/FALSE macros for local variables. This aligns with standard C++ practices and the Google C++ Style Guide.

  bool enable_impeller = fl_dart_project_get_enable_impeller(self->project);
  bool has_enable_impeller = false;
  for (const auto& env_switch : flutter::GetSwitchesFromEnvironment()) {
    if (env_switch == "--enable-impeller" ||
        env_switch == "--enable-impeller=true") {
      enable_impeller = true;
      has_enable_impeller = true;
    } else if (env_switch == "--enable-impeller=false") {
      enable_impeller = false;
      has_enable_impeller = true;
    }
  }
References
  1. C++ code should follow the Google C++ Style Guide, which prefers standard C++ types (like bool) over platform-specific typedefs (like gboolean) for local variables. (link)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a reasonable suggestion, but I'd defer to @robert-ancell here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is using gboolean so it's correct to use TRUE/FALSE instead (this is done elsewhere).


g_autoptr(GPtrArray) command_line_args =
g_ptr_array_new_with_free_func(g_free);
g_ptr_array_insert(command_line_args, 0, g_strdup("flutter"));
Expand All @@ -821,6 +834,10 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
// Linux (and other desktop platforms) always uses SDFs.
g_ptr_array_add(command_line_args, g_strdup("--impeller-use-sdfs"));

if (enable_impeller && !has_enable_impeller) {
g_ptr_array_add(command_line_args, g_strdup("--enable-impeller"));
}

gchar** dart_entrypoint_args =
fl_dart_project_get_dart_entrypoint_arguments(self->project);

Expand Down
30 changes: 30 additions & 0 deletions engine/src/flutter/shell/platform/linux/fl_engine_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,36 @@ TEST(FlEngineTest, SendKeyEventError) {
EXPECT_TRUE(called);
}

TEST(FlEngineTest, EnableImpeller) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_enable_impeller(project, TRUE);
g_autoptr(FlEngine) engine = fl_engine_new(project);

bool called = false;
fl_engine_get_embedder_api(engine)->Initialize = MOCK_ENGINE_PROC(
Initialize,
([&called](size_t version, const FlutterRendererConfig* config,
const FlutterProjectArgs* args, void* user_data,
FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
called = true;
bool has_impeller_switch = false;
for (int i = 0; i < args->command_line_argc; i++) {
if (strcmp(args->command_line_argv[i], "--enable-impeller") == 0) {
has_impeller_switch = true;
}
}
EXPECT_TRUE(has_impeller_switch);
return kSuccess;
}));
fl_engine_get_embedder_api(engine)->RunInitialized =
MOCK_ENGINE_PROC(RunInitialized, ([](auto engine) { return kSuccess; }));

g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr);
EXPECT_TRUE(called);
}

TEST(FlEngineTest, ChildObjects) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,24 @@ void fl_dart_project_set_ui_thread_policy(FlDartProject* project,
*/
FlUIThreadPolicy fl_dart_project_get_ui_thread_policy(FlDartProject* project);

/**
* fl_dart_project_set_enable_impeller:
* @project: an #FlDartProject.
* @enable_impeller: whether to enable the Impeller renderer.
*
* Sets whether the Impeller renderer should be enabled.
*/
void fl_dart_project_set_enable_impeller(FlDartProject* project,
gboolean enable_impeller);

/**
* fl_dart_project_get_enable_impeller:
* @project: an #FlDartProject.
*
* Returns: %TRUE if the Impeller renderer is enabled.
*/
gboolean fl_dart_project_get_enable_impeller(FlDartProject* project);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_PUBLIC_FLUTTER_LINUX_FL_DART_PROJECT_H_
3 changes: 2 additions & 1 deletion packages/flutter_tools/lib/src/desktop_device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,9 @@ abstract class DesktopDevice extends Device {
case ImpellerStatus.enabled:
addFlag('enable-impeller=true');
case ImpellerStatus.disabled:
case ImpellerStatus.platformDefault:
addFlag('enable-impeller=false');
case ImpellerStatus.platformDefault:
break;
}
if (debuggingOptions.enableFlutterGpu) {
addFlag('enable-flutter-gpu=true');
Expand Down
18 changes: 8 additions & 10 deletions packages/flutter_tools/test/general.shard/desktop_device_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,13 @@ void main() {
'FLUTTER_ENGINE_SWITCH_11': 'endless-trace-buffer=true',
'FLUTTER_ENGINE_SWITCH_12': 'profile-microtasks=true',
'FLUTTER_ENGINE_SWITCH_13': 'purge-persistent-cache=true',
'FLUTTER_ENGINE_SWITCH_14': 'enable-impeller=false',
'FLUTTER_ENGINE_SWITCH_15': 'enable-checked-mode=true',
'FLUTTER_ENGINE_SWITCH_16': 'verify-entry-points=true',
'FLUTTER_ENGINE_SWITCH_17': 'start-paused=true',
'FLUTTER_ENGINE_SWITCH_18': 'disable-service-auth-codes=true',
'FLUTTER_ENGINE_SWITCH_19': 'use-test-fonts=true',
'FLUTTER_ENGINE_SWITCH_20': 'verbose-logging=true',
'FLUTTER_ENGINE_SWITCHES': '20',
'FLUTTER_ENGINE_SWITCH_14': 'enable-checked-mode=true',
'FLUTTER_ENGINE_SWITCH_15': 'verify-entry-points=true',
'FLUTTER_ENGINE_SWITCH_16': 'start-paused=true',
'FLUTTER_ENGINE_SWITCH_17': 'disable-service-auth-codes=true',
'FLUTTER_ENGINE_SWITCH_18': 'use-test-fonts=true',
'FLUTTER_ENGINE_SWITCH_19': 'verbose-logging=true',
'FLUTTER_ENGINE_SWITCHES': '19',
},
),
]);
Expand Down Expand Up @@ -223,8 +222,7 @@ void main() {
'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true',
'FLUTTER_ENGINE_SWITCH_2': 'trace-startup=true',
'FLUTTER_ENGINE_SWITCH_3': 'trace-allowlist=foo,bar',
'FLUTTER_ENGINE_SWITCH_4': 'enable-impeller=false',
'FLUTTER_ENGINE_SWITCHES': '4',
'FLUTTER_ENGINE_SWITCHES': '3',
},
),
]);
Expand Down
Loading