Skip to content

Commit 7e443a7

Browse files
committed
Initial Commit.
0 parents  commit 7e443a7

18 files changed

+998
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
out/
2+
sdk/

.gn

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
buildconfig = "//build/BUILDCONFIG.gn"

BUILD.gn

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
group("default") {
2+
deps = [
3+
"flutter",
4+
]
5+
}

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Notes:
2+
------
3+
* This is the companion repository for the `Flutter on Raspberry Pi (mostly) from Scratch` article.
4+
* This repository is only meant to showcase what a minimal embedder might look like. It is not production ready (and never will be)!
5+
* Though this repository is based off of the [Raspberry Pi Cross Compilation SDK and GN Sysroot](https://github.com/chinmaygarde/raspberrypi_cross_compilation_sdk) repository. You dont have to use this toolchain or GN.
6+
7+
Flutter on Raspberry Pi
8+
=======================
9+
10+
Flutter Embedder for Pi using Broadcom APIs directly.
11+
12+
Prerequisites
13+
-------------
14+
15+
* The `libflutter_engine.so`, `icudtl.dat` and `flutter_embedder.h` files for the Raspberry Pi placed in the `out/` directory.
16+
17+
Usage for Raspberry Pi
18+
----------------------
19+
20+
* Download the prepared toolchain, sysroot and related tools to the `out` directory `./tools/setup_sdk.sh`.
21+
* This takes a while and downloads upto 1 GB of data from cloud storage.
22+
* Prepare the build output directory `out` with paths to your toolchain using `./tools/setup_gn.sh`.
23+
* Build using `ninja -C out` on your host.
24+
* Hack and repeat.
25+
* Push your executable to the Raspberry Pi and run.
26+
* You should probably mount the `out` directory to the remote Raspberry Pi using SSHFS. That way, the build artifacts automatically end up getting pushed to the Pi.

build/BUILDCONFIG.gn

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
set_default_toolchain("//build/toolchain/clang")
2+
3+
set_defaults("executable") {
4+
configs = [ "//build/config/compiler" ]
5+
}

build/config/compiler/BUILD.gn

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
config("compiler") {
2+
# Defaults that will be filled in later below.
3+
cflags = []
4+
cflags_c = []
5+
cflags_cc = []
6+
ldflags = []
7+
8+
cflags_c += [ "-std=c99" ]
9+
10+
cflags_cc += [ "-std=c++14" ]
11+
12+
libs = [
13+
"m",
14+
"stdc++",
15+
]
16+
}

build/toolchain/clang/BUILD.gn

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
declare_args() {
2+
# The path to functional Clang toolchain capable of building the target triple.
3+
toolchain_path = ""
4+
5+
# The path to the target system sysroot.
6+
sysroot_path = ""
7+
8+
# The target. For example: "arm-linux-gnueabihf" for Raspberry Pi.
9+
target_triple = ""
10+
11+
# Path to extra system inlcude directories relative to the sysroot. For example: "/opt/vc/include" on the Raspberry Pi.
12+
extra_system_include_dirs = []
13+
14+
# Path to extra system library directories relative to the sysroot. For example: "/opt/vc/lib" on the Raspberry Pi.
15+
extra_system_lib_dirs = []
16+
}
17+
18+
toolchain("clang") {
19+
assert(toolchain_path != "", "Toolchain must be specified")
20+
assert(sysroot_path != "", "Sysroot must be specified")
21+
assert(target_triple != "", "Target triple must be specified")
22+
23+
# Setup path to the tools to invoke
24+
cc = "${toolchain_path}/bin/clang"
25+
cxx = "${toolchain_path}/bin/clang++"
26+
ld = "${toolchain_path}/bin/clang"
27+
ar = "${toolchain_path}/bin/$target_triple-ar"
28+
29+
# Setup the target triple, this needs is required for all tool invocations.
30+
target_triple_command = "--target=$target_triple"
31+
sysroot_command = "--sysroot $sysroot_path"
32+
33+
# Add extra headers search directories for folders relative to sysroot.
34+
extra_system_include_dirs_string = "-isysroot $sysroot_path"
35+
foreach(extra_system_include_dir, extra_system_include_dirs) {
36+
extra_system_include_dirs_string = "$extra_system_include_dirs_string -iwithsysroot $extra_system_include_dir"
37+
}
38+
39+
# Add extra library search paths for folders relative to sysroot.
40+
extra_system_lib_dirs_string = ""
41+
foreach(extra_system_lib_dir, extra_system_lib_dirs) {
42+
extra_system_lib_dirs_string =
43+
"$extra_system_lib_dirs_string -L$sysroot_path/$extra_system_lib_dir"
44+
}
45+
46+
# Common GN verbiage.
47+
lib_switch = "-l"
48+
lib_dir_switch = "-L"
49+
50+
# Common description prefixes in non-verbose invocation.
51+
pretty_build_prefix = "🔨 "
52+
pretty_link_prefix = "⛓️ "
53+
54+
tool("cc") {
55+
depfile = "{{output}}.deps"
56+
command = "$cc -o {{output}} -MMD -MF $depfile -c $target_triple_command $sysroot_command $extra_system_include_dirs_string {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} {{source}}"
57+
outputs = [
58+
"{{target_out_dir}}/{{target_output_name}}/objects/{{source_name_part}}.o",
59+
]
60+
depsformat = "gcc"
61+
description = "$pretty_build_prefix CC: {{source_name_part}}"
62+
}
63+
64+
tool("cxx") {
65+
depfile = "{{output}}.deps"
66+
command = "$cxx -o {{output}} -MMD -MF $depfile -c $target_triple_command $sysroot_command $extra_system_include_dirs_string {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} {{source}}"
67+
outputs = [
68+
"{{target_out_dir}}/{{target_output_name}}/objects/{{source_name_part}}.o",
69+
]
70+
depsformat = "gcc"
71+
description = "$pretty_build_prefix CXX: {{source_name_part}}"
72+
}
73+
74+
tool("alink") {
75+
rspfile = "{{output}}.rsp"
76+
command = "rm -f {{output}} && $ar rcs {{output}} @$rspfile"
77+
description = "$pretty_link_prefix Archive: {{output}}"
78+
rspfile_content = "{{inputs}}"
79+
outputs = [
80+
"{{target_out_dir}}/{{target_output_name}}{{output_extension}}",
81+
]
82+
default_output_extension = ".a"
83+
output_prefix = "lib"
84+
}
85+
86+
tool("solink") {
87+
outfile = "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"
88+
rspfile = "{{output}}.rsp"
89+
rspfile_content = "{{inputs}}"
90+
command = "$ld -o $outfile $target_triple_command $sysroot_command $extra_system_lib_dirs_string -shared {{ldflags}} {{solibs}} {{libs}} @$rspfile"
91+
description = "$pretty_link_prefix Shared Library: {{output}}"
92+
outputs = [
93+
outfile,
94+
]
95+
default_output_extension = ".so"
96+
output_prefix = "lib"
97+
}
98+
99+
tool("link") {
100+
outfile = "{{target_out_dir}}/{{target_output_name}}/executables/{{target_output_name}}"
101+
rspfile = "$outfile.rsp"
102+
rspfile_content = "{{inputs}}"
103+
command = "$ld -o $outfile $target_triple_command $sysroot_command $extra_system_lib_dirs_string {{ldflags}} {{solibs}} {{libs}} @$rspfile"
104+
outputs = [
105+
outfile,
106+
]
107+
description = "$pretty_link_prefix Link Executable: {{target_output_name}}"
108+
}
109+
110+
tool("stamp") {
111+
command = "touch {{output}}"
112+
description = "$pretty_build_prefix Stamp: {{output}}"
113+
}
114+
}

flutter/BUILD.gn

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
executable("flutter") {
3+
include_dirs = [
4+
root_out_dir
5+
]
6+
7+
lib_dirs = [
8+
root_out_dir
9+
]
10+
11+
sources = [
12+
"flutter_application.h",
13+
"flutter_application.cc",
14+
"macros.h",
15+
"main.cc",
16+
"utils.cc",
17+
"utils.h",
18+
"pi_display.h",
19+
"pi_display.cc",
20+
]
21+
22+
libs = [
23+
"rt",
24+
"brcmEGL",
25+
"brcmGLESv2",
26+
"flutter_engine",
27+
"pthread",
28+
"dl",
29+
"bcm_host",
30+
"vcos",
31+
"vchiq_arm",
32+
]
33+
}

flutter/flutter_application.cc

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright 2018 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 "flutter_application.h"
6+
7+
#include <sys/types.h>
8+
9+
#include <chrono>
10+
#include <sstream>
11+
#include <vector>
12+
13+
#include "utils.h"
14+
15+
namespace flutter {
16+
17+
static_assert(FLUTTER_ENGINE_VERSION == 1, "");
18+
19+
static const char *kICUDataFileName = "icudtl.dat";
20+
21+
static std::string GetICUDataPath() {
22+
auto exe_dir = GetExecutableDirectory();
23+
if (exe_dir == "") {
24+
return "";
25+
}
26+
std::stringstream stream;
27+
stream << exe_dir << kICUDataFileName;
28+
29+
auto icu_path = stream.str();
30+
31+
if (!FileExistsAtPath(icu_path.c_str())) {
32+
FLWAY_ERROR << "Could not find " << icu_path << std::endl;
33+
return "";
34+
}
35+
36+
return icu_path;
37+
}
38+
39+
FlutterApplication::FlutterApplication(
40+
std::string bundle_path, const std::vector<std::string> &command_line_args,
41+
RenderDelegate &render_delegate)
42+
: render_delegate_(render_delegate) {
43+
if (!FlutterAssetBundleIsValid(bundle_path)) {
44+
FLWAY_ERROR << "Flutter asset bundle was not valid." << std::endl;
45+
return;
46+
}
47+
48+
FlutterRendererConfig config = {};
49+
config.type = kOpenGL;
50+
config.open_gl.struct_size = sizeof(config.open_gl);
51+
config.open_gl.make_current = [](void *userdata) -> bool {
52+
return reinterpret_cast<FlutterApplication *>(userdata)
53+
->render_delegate_.OnApplicationContextMakeCurrent();
54+
};
55+
config.open_gl.clear_current = [](void *userdata) -> bool {
56+
return reinterpret_cast<FlutterApplication *>(userdata)
57+
->render_delegate_.OnApplicationContextClearCurrent();
58+
};
59+
config.open_gl.present = [](void *userdata) -> bool {
60+
return reinterpret_cast<FlutterApplication *>(userdata)
61+
->render_delegate_.OnApplicationPresent();
62+
};
63+
config.open_gl.fbo_callback = [](void *userdata) -> uint32_t {
64+
return reinterpret_cast<FlutterApplication *>(userdata)
65+
->render_delegate_.OnApplicationGetOnscreenFBO();
66+
};
67+
config.open_gl.gl_proc_resolver =
68+
[&render_delegate_](void *userdata, const char *name) -> void * {
69+
return reinterpret_cast<FlutterApplication *>(userdata)
70+
->render_delegate_.GetProcAddress(name);
71+
};
72+
73+
auto icu_data_path = GetICUDataPath();
74+
75+
if (icu_data_path == "") {
76+
FLWAY_ERROR << "Could not find ICU data. It should be placed next to the "
77+
"executable but it wasn't there."
78+
<< std::endl;
79+
return;
80+
}
81+
82+
std::vector<const char *> command_line_args_c;
83+
84+
for (const auto &arg : command_line_args) {
85+
command_line_args_c.push_back(arg.c_str());
86+
}
87+
88+
FlutterProjectArgs args = {
89+
.struct_size = sizeof(FlutterProjectArgs),
90+
.assets_path = bundle_path.c_str(),
91+
.main_path = "",
92+
.packages_path = "",
93+
.icu_data_path = icu_data_path.c_str(),
94+
.command_line_argc = static_cast<int>(command_line_args_c.size()),
95+
.command_line_argv = command_line_args_c.data(),
96+
};
97+
98+
FlutterEngine engine = nullptr;
99+
auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args,
100+
this /* userdata */, &engine_);
101+
102+
if (result != kSuccess) {
103+
FLWAY_ERROR << "Could not run the Flutter engine" << std::endl;
104+
return;
105+
}
106+
107+
valid_ = true;
108+
}
109+
110+
FlutterApplication::~FlutterApplication() {
111+
if (engine_ == nullptr) {
112+
return;
113+
}
114+
115+
auto result = FlutterEngineShutdown(engine_);
116+
117+
if (result != kSuccess) {
118+
FLWAY_ERROR << "Could not shutdown the Flutter engine." << std::endl;
119+
}
120+
}
121+
122+
bool FlutterApplication::IsValid() const { return valid_; }
123+
124+
bool FlutterApplication::SetWindowSize(size_t width, size_t height) {
125+
FlutterWindowMetricsEvent event = {};
126+
event.struct_size = sizeof(event);
127+
event.width = width;
128+
event.height = height;
129+
event.pixel_ratio = 1.0;
130+
return FlutterEngineSendWindowMetricsEvent(engine_, &event) == kSuccess;
131+
}
132+
133+
void FlutterApplication::ProcessEvents() {
134+
__FlutterEngineFlushPendingTasksNow();
135+
}
136+
137+
bool FlutterApplication::SendPointerEvent(int button, int x, int y) {
138+
if (!valid_) {
139+
FLWAY_ERROR << "Pointer events on an invalid application." << std::endl;
140+
return false;
141+
}
142+
143+
// Simple hover event. Nothing to do.
144+
if (last_button_ == 0 && button == 0) {
145+
return true;
146+
}
147+
148+
FlutterPointerPhase phase = kCancel;
149+
150+
if (last_button_ == 0 && button != 0) {
151+
phase = kDown;
152+
} else if (last_button_ == button) {
153+
phase = kMove;
154+
} else {
155+
phase = kUp;
156+
}
157+
158+
last_button_ = button;
159+
return SendFlutterPointerEvent(phase, x, y);
160+
}
161+
162+
bool FlutterApplication::SendFlutterPointerEvent(FlutterPointerPhase phase,
163+
double x, double y) {
164+
FlutterPointerEvent event = {};
165+
event.struct_size = sizeof(event);
166+
event.phase = phase;
167+
event.x = x;
168+
event.y = y;
169+
event.timestamp =
170+
std::chrono::duration_cast<std::chrono::microseconds>(
171+
std::chrono::high_resolution_clock::now().time_since_epoch())
172+
.count();
173+
return FlutterEngineSendPointerEvent(engine_, &event, 1) == kSuccess;
174+
}
175+
176+
void FlutterApplication::ReadInputEvents() {
177+
// TODO(chinmaygarde): Fill this in for touch screen and not just devices that
178+
// fake mice.
179+
::sleep(INT_MAX);
180+
}
181+
182+
} // namespace flutter

0 commit comments

Comments
 (0)