├── .gitignore ├── .gn ├── BUILD.gn ├── README.md ├── build ├── BUILDCONFIG.gn ├── config │ └── compiler │ │ └── BUILD.gn └── toolchain │ └── clang │ └── BUILD.gn ├── flutter ├── BUILD.gn ├── flutter_application.cc ├── flutter_application.h ├── macros.h ├── main.cc ├── pi_display.cc ├── pi_display.h ├── utils.cc └── utils.h └── tools ├── setup_gn.sh └── setup_sdk.sh /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | sdk/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.gn: -------------------------------------------------------------------------------- 1 | 2 | buildconfig = "//build/BUILDCONFIG.gn" 3 | -------------------------------------------------------------------------------- /BUILD.gn: -------------------------------------------------------------------------------- 1 | group("default") { 2 | deps = [ 3 | "flutter", 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Notes: 2 | ------ 3 | * This is the companion repository for the `Flutter on Raspberry Pi (mostly) from Scratch` [article](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1). 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. 27 | -------------------------------------------------------------------------------- /build/BUILDCONFIG.gn: -------------------------------------------------------------------------------- 1 | set_default_toolchain("//build/toolchain/clang") 2 | 3 | set_defaults("executable") { 4 | configs = [ "//build/config/compiler" ] 5 | } 6 | -------------------------------------------------------------------------------- /build/config/compiler/BUILD.gn: -------------------------------------------------------------------------------- 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 | } 17 | -------------------------------------------------------------------------------- /build/toolchain/clang/BUILD.gn: -------------------------------------------------------------------------------- 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 | } 115 | -------------------------------------------------------------------------------- /flutter/BUILD.gn: -------------------------------------------------------------------------------- 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 | } 34 | -------------------------------------------------------------------------------- /flutter/flutter_application.cc: -------------------------------------------------------------------------------- 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 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "utils.h" 16 | 17 | namespace flutter { 18 | 19 | static_assert(FLUTTER_ENGINE_VERSION == 1, ""); 20 | 21 | static const char *kICUDataFileName = "icudtl.dat"; 22 | 23 | static std::string GetICUDataPath() { 24 | auto exe_dir = GetExecutableDirectory(); 25 | if (exe_dir == "") { 26 | return ""; 27 | } 28 | std::stringstream stream; 29 | stream << exe_dir << kICUDataFileName; 30 | 31 | auto icu_path = stream.str(); 32 | 33 | if (!FileExistsAtPath(icu_path.c_str())) { 34 | FLWAY_ERROR << "Could not find " << icu_path << std::endl; 35 | return ""; 36 | } 37 | 38 | return icu_path; 39 | } 40 | 41 | FlutterApplication::FlutterApplication( 42 | std::string bundle_path, const std::vector &command_line_args, 43 | RenderDelegate &render_delegate) 44 | : render_delegate_(render_delegate) { 45 | if (!FlutterAssetBundleIsValid(bundle_path)) { 46 | FLWAY_ERROR << "Flutter asset bundle was not valid." << std::endl; 47 | return; 48 | } 49 | 50 | FlutterRendererConfig config = {}; 51 | config.type = kOpenGL; 52 | config.open_gl.struct_size = sizeof(config.open_gl); 53 | config.open_gl.make_current = [](void *userdata) -> bool { 54 | return reinterpret_cast(userdata) 55 | ->render_delegate_.OnApplicationContextMakeCurrent(); 56 | }; 57 | config.open_gl.clear_current = [](void *userdata) -> bool { 58 | return reinterpret_cast(userdata) 59 | ->render_delegate_.OnApplicationContextClearCurrent(); 60 | }; 61 | config.open_gl.present = [](void *userdata) -> bool { 62 | return reinterpret_cast(userdata) 63 | ->render_delegate_.OnApplicationPresent(); 64 | }; 65 | config.open_gl.fbo_callback = [](void *userdata) -> uint32_t { 66 | return reinterpret_cast(userdata) 67 | ->render_delegate_.OnApplicationGetOnscreenFBO(); 68 | }; 69 | config.open_gl.gl_proc_resolver = [](void *userdata, 70 | const char *name) -> void * { 71 | return reinterpret_cast(userdata) 72 | ->render_delegate_.GetProcAddress(name); 73 | }; 74 | 75 | auto icu_data_path = GetICUDataPath(); 76 | 77 | if (icu_data_path == "") { 78 | FLWAY_ERROR << "Could not find ICU data. It should be placed next to the " 79 | "executable but it wasn't there." 80 | << std::endl; 81 | return; 82 | } 83 | 84 | std::vector command_line_args_c; 85 | 86 | for (const auto &arg : command_line_args) { 87 | command_line_args_c.push_back(arg.c_str()); 88 | } 89 | 90 | FlutterProjectArgs args = { 91 | .struct_size = sizeof(FlutterProjectArgs), 92 | .assets_path = bundle_path.c_str(), 93 | .icu_data_path = icu_data_path.c_str(), 94 | .command_line_argc = static_cast(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( 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 183 | -------------------------------------------------------------------------------- /flutter/flutter_application.h: -------------------------------------------------------------------------------- 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 | #pragma once 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "macros.h" 13 | 14 | namespace flutter { 15 | 16 | class FlutterApplication { 17 | public: 18 | class RenderDelegate { 19 | public: 20 | virtual bool OnApplicationContextMakeCurrent() = 0; 21 | 22 | virtual bool OnApplicationContextClearCurrent() = 0; 23 | 24 | virtual bool OnApplicationPresent() = 0; 25 | 26 | virtual uint32_t OnApplicationGetOnscreenFBO() = 0; 27 | 28 | virtual void *GetProcAddress(const char *) = 0; 29 | }; 30 | 31 | FlutterApplication(std::string bundle_path, 32 | const std::vector &args, 33 | RenderDelegate &render_delegate); 34 | 35 | ~FlutterApplication(); 36 | 37 | bool IsValid() const; 38 | 39 | void ProcessEvents(); 40 | 41 | bool SetWindowSize(size_t width, size_t height); 42 | 43 | bool SendPointerEvent(int button, int x, int y); 44 | 45 | void ReadInputEvents(); 46 | 47 | private: 48 | bool valid_; 49 | RenderDelegate &render_delegate_; 50 | FlutterEngine engine_ = nullptr; 51 | int last_button_ = 0; 52 | 53 | bool SendFlutterPointerEvent(FlutterPointerPhase phase, double x, double y); 54 | 55 | FLWAY_DISALLOW_COPY_AND_ASSIGN(FlutterApplication); 56 | }; 57 | 58 | } // namespace flutter 59 | -------------------------------------------------------------------------------- /flutter/macros.h: -------------------------------------------------------------------------------- 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 | #pragma once 6 | 7 | #include 8 | 9 | #define FLWAY_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete; 10 | 11 | #define FLWAY_DISALLOW_ASSIGN(TypeName) \ 12 | void operator=(const TypeName&) = delete; 13 | 14 | #define FLWAY_DISALLOW_COPY_AND_ASSIGN(TypeName) \ 15 | FLWAY_DISALLOW_COPY(TypeName) \ 16 | FLWAY_DISALLOW_ASSIGN(TypeName) 17 | 18 | #define __FLWAY_LINE_PREFIX << __FILE__ << ":" << __LINE__ << ": " 19 | #define FLWAY_LOG std::cout << "LOG: " __FLWAY_LINE_PREFIX 20 | #define FLWAY_ERROR std::cerr << "ERROR: " __FLWAY_LINE_PREFIX 21 | #define FLWAY_WIP \ 22 | std::cerr << "Work In Progress. Aborting." << std::endl; \ 23 | abort(); 24 | -------------------------------------------------------------------------------- /flutter/main.cc: -------------------------------------------------------------------------------- 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 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "flutter_application.h" 12 | #include "pi_display.h" 13 | #include "utils.h" 14 | 15 | namespace flutter { 16 | 17 | static void PrintUsage() { 18 | std::cerr << "Flutter Raspberry Pi" << std::endl << std::endl; 19 | std::cerr << "========================" << std::endl; 20 | std::cerr << "Usage: `" << GetExecutableName() 21 | << " `" << std::endl 22 | << std::endl; 23 | std::cerr << R"~( 24 | This utility runs an instance of a Flutter application and renders using 25 | Video Core APIs. 26 | 27 | The Flutter tools can be obtained at https://flutter.io/ 28 | 29 | asset_bundle_path: The Flutter application code needs to be snapshotted using 30 | the Flutter tools and the assets packaged in the appropriate 31 | location. This can be done for any Flutter application by 32 | running `flutter build bundle` while in the directory of a 33 | valid Flutter project. This should package all the code and 34 | assets in the "build/flutter_assets" directory. Specify this 35 | directory as the first argument to this utility. 36 | 37 | flutter_flags: Typically empty. These extra flags are passed directly to the 38 | Flutter engine. To see all supported flags, run 39 | `flutter_tester --help` using the test binary included in the 40 | Flutter tools. 41 | )~" << std::endl; 42 | } 43 | 44 | static bool Main(std::vector args) { 45 | if (args.size() == 0) { 46 | std::cerr << " " << std::endl; 47 | PrintUsage(); 48 | return false; 49 | } 50 | 51 | const auto asset_bundle_path = args[0]; 52 | 53 | if (!FlutterAssetBundleIsValid(asset_bundle_path)) { 54 | std::cerr << " " << std::endl; 55 | PrintUsage(); 56 | return false; 57 | } 58 | 59 | PiDisplay display; 60 | 61 | if (!display.IsValid()) { 62 | FLWAY_ERROR << "Could not initialize the display." << std::endl; 63 | return false; 64 | } 65 | 66 | FLWAY_LOG << "Display Size: " << display.GetWidth() << " x " 67 | << display.GetHeight() << std::endl; 68 | 69 | FlutterApplication application(asset_bundle_path, args, display); 70 | if (!application.IsValid()) { 71 | FLWAY_ERROR << "Flutter application was not valid." << std::endl; 72 | return false; 73 | } 74 | 75 | if (!application.SetWindowSize(display.GetWidth(), display.GetHeight())) { 76 | FLWAY_ERROR << "Could not update Flutter application size." << std::endl; 77 | return false; 78 | } 79 | 80 | application.ReadInputEvents(); 81 | 82 | return true; 83 | } 84 | 85 | } // namespace flutter 86 | 87 | int main(int argc, char *argv[]) { 88 | std::vector args; 89 | for (int i = 1; i < argc; ++i) { 90 | args.push_back(argv[i]); 91 | } 92 | return flutter::Main(std::move(args)) ? EXIT_SUCCESS : EXIT_FAILURE; 93 | } 94 | -------------------------------------------------------------------------------- /flutter/pi_display.cc: -------------------------------------------------------------------------------- 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 "pi_display.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace flutter { 11 | 12 | PiDisplay::PiDisplay() { 13 | bcm_host_init(); 14 | 15 | // Setup the EGL Display. 16 | { 17 | auto display = ::eglGetDisplay(EGL_DEFAULT_DISPLAY); 18 | if (display == EGL_NO_DISPLAY) { 19 | FLWAY_ERROR << "Could not get the EGL display." << std::endl; 20 | return; 21 | } 22 | 23 | if (::eglInitialize(display, nullptr, nullptr) != EGL_TRUE) { 24 | FLWAY_ERROR << "Could not initialize the EGL display." << std::endl; 25 | return; 26 | } 27 | 28 | display_ = display; 29 | } 30 | 31 | // Choose an EGL config. 32 | EGLConfig config = {0}; 33 | 34 | { 35 | EGLint num_config = 0; 36 | const EGLint attribute_list[] = {EGL_RED_SIZE, 8, 37 | EGL_GREEN_SIZE, 8, 38 | EGL_BLUE_SIZE, 8, 39 | EGL_ALPHA_SIZE, 8, 40 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 41 | EGL_NONE}; 42 | 43 | if (::eglChooseConfig(display_, attribute_list, &config, 1, &num_config) != 44 | EGL_TRUE) { 45 | FLWAY_ERROR << "Could not choose an EGL config." << std::endl; 46 | return; 47 | } 48 | } 49 | 50 | // Create the EGL context. 51 | { 52 | const EGLint context_attributes[] = { 53 | EGL_CONTEXT_CLIENT_VERSION, // 54 | 2, // 55 | EGL_NONE // 56 | }; 57 | auto context = ::eglCreateContext(display_, config, EGL_NO_CONTEXT, 58 | context_attributes); 59 | 60 | if (context == EGL_NO_CONTEXT) { 61 | FLWAY_ERROR << "Could not create the EGL context." << std::endl; 62 | return; 63 | } 64 | 65 | context_ = context; 66 | } 67 | 68 | // Query the size of the current display. 69 | { 70 | uint32_t display_width = 0; 71 | uint32_t display_height = 0; 72 | 73 | if (graphics_get_display_size(0 /* LCD */, &display_width, 74 | &display_height) < 0) { 75 | FLWAY_ERROR << "Could not query display size." << std::endl; 76 | return; 77 | } 78 | 79 | if (display_height <= 0 || display_width <= 0) { 80 | FLWAY_ERROR << "Invalid display size: " << display_width << " x " 81 | << display_height << std::endl; 82 | return; 83 | } 84 | 85 | display_width_ = display_width; 86 | display_height_ = display_height; 87 | } 88 | 89 | // VideoCore Fu. 90 | // TODO(chinmaygarde): There is insufficient error handler in this block on 91 | // all the call to vc_. 92 | { 93 | dispman_display_ = ::vc_dispmanx_display_open(0 /* LCD */); 94 | 95 | DISPMANX_UPDATE_HANDLE_T dispman_update = ::vc_dispmanx_update_start(0); 96 | 97 | const VC_RECT_T destination_rect = { 98 | .x = 0, 99 | .y = 0, 100 | .width = display_width_, 101 | .height = display_height_, 102 | }; 103 | 104 | const VC_RECT_T source_rect = { 105 | .x = 0, 106 | .y = 0, 107 | .width = display_width_ << 16, 108 | .height = display_height_ << 16, 109 | }; 110 | 111 | dispman_element_ = 112 | ::vc_dispmanx_element_add(dispman_update, // update 113 | dispman_display_, // display 114 | 0, // layer 115 | &destination_rect, // destination rect 116 | 0, // source handle 117 | &source_rect, // source rect 118 | DISPMANX_PROTECTION_NONE, // protections 119 | 0, // alpha 120 | 0, // clang 121 | DISPMANX_NO_ROTATE // transform 122 | ); 123 | 124 | native_window_.element = dispman_element_; 125 | native_window_.width = display_width_; 126 | native_window_.height = display_height_; 127 | 128 | ::vc_dispmanx_update_submit_sync(dispman_update); 129 | } 130 | 131 | // Create the EGL window surface. 132 | { 133 | auto surface = 134 | ::eglCreateWindowSurface(display_, config, &native_window_, nullptr); 135 | if (surface == EGL_NO_SURFACE) { 136 | FLWAY_ERROR << "Could not create EGL surface." << std::endl; 137 | return; 138 | } 139 | 140 | surface_ = surface; 141 | } 142 | 143 | valid_ = true; 144 | } 145 | 146 | PiDisplay::~PiDisplay() { 147 | if (surface_ != EGL_NO_SURFACE) { 148 | ::eglDestroySurface(display_, surface_); 149 | surface_ = EGL_NO_SURFACE; 150 | } 151 | 152 | // TODO(chinmaygarde): There is insufficient error handling here and the 153 | // element resource lifecycle is unclear. 154 | ::vc_dispmanx_display_close(dispman_display_); 155 | 156 | if (context_ != EGL_NO_CONTEXT) { 157 | ::eglDestroyContext(display_, context_); 158 | context_ = EGL_NO_CONTEXT; 159 | } 160 | 161 | if (display_ != EGL_NO_DISPLAY) { 162 | ::eglTerminate(display_); 163 | display_ = EGL_NO_DISPLAY; 164 | } 165 | 166 | ::bcm_host_deinit(); 167 | } 168 | 169 | bool PiDisplay::IsValid() const { return valid_; } 170 | 171 | size_t PiDisplay::GetWidth() const { return display_width_; } 172 | 173 | size_t PiDisplay::GetHeight() const { return display_height_; } 174 | 175 | // |FlutterApplication::RenderDelegate| 176 | bool PiDisplay::OnApplicationContextMakeCurrent() { 177 | if (!valid_) { 178 | FLWAY_ERROR << "Cannot make an invalid display current." << std::endl; 179 | return false; 180 | } 181 | if (::eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) { 182 | FLWAY_ERROR << "Could not make the context current." << std::endl; 183 | 184 | return false; 185 | } 186 | return true; 187 | } 188 | 189 | // |FlutterApplication::RenderDelegate| 190 | bool PiDisplay::OnApplicationContextClearCurrent() { 191 | if (!valid_) { 192 | FLWAY_ERROR << "Cannot clear an invalid display." << std::endl; 193 | return false; 194 | } 195 | if (::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, 196 | EGL_NO_CONTEXT) != EGL_TRUE) { 197 | FLWAY_ERROR << "Could not clear the current context." << std::endl; 198 | return false; 199 | } 200 | return true; 201 | } 202 | 203 | // |FlutterApplication::RenderDelegate| 204 | bool PiDisplay::OnApplicationPresent() { 205 | if (!valid_) { 206 | FLWAY_ERROR << "Cannot present an invalid display." << std::endl; 207 | return false; 208 | } 209 | 210 | if (::eglSwapBuffers(display_, surface_) != EGL_TRUE) { 211 | FLWAY_ERROR << "Could not swap buffers to present the screen." << std::endl; 212 | return false; 213 | } 214 | 215 | return true; 216 | } 217 | 218 | // |FlutterApplication::RenderDelegate| 219 | uint32_t PiDisplay::OnApplicationGetOnscreenFBO() { 220 | // Just FBO0. 221 | return 0; 222 | } 223 | 224 | // |FlutterApplication::RenderDelegate| 225 | void *PiDisplay::GetProcAddress(const char *name) { 226 | if (name == nullptr) { 227 | return nullptr; 228 | } 229 | 230 | if (auto address = dlsym(RTLD_DEFAULT, name)) { 231 | return address; 232 | } 233 | 234 | return nullptr; 235 | } 236 | 237 | } // namespace flutter 238 | -------------------------------------------------------------------------------- /flutter/pi_display.h: -------------------------------------------------------------------------------- 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 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "flutter_application.h" 12 | #include "macros.h" 13 | 14 | namespace flutter { 15 | 16 | class PiDisplay : public FlutterApplication::RenderDelegate { 17 | public: 18 | PiDisplay(); 19 | 20 | ~PiDisplay(); 21 | 22 | bool IsValid() const; 23 | 24 | size_t GetWidth() const; 25 | 26 | size_t GetHeight() const; 27 | 28 | private: 29 | int32_t display_width_ = 0; 30 | int32_t display_height_ = 0; 31 | EGLDisplay display_ = EGL_NO_DISPLAY; 32 | EGLContext context_ = EGL_NO_CONTEXT; 33 | EGLSurface surface_ = EGL_NO_SURFACE; 34 | DISPMANX_DISPLAY_HANDLE_T dispman_display_ = {0}; 35 | DISPMANX_ELEMENT_HANDLE_T dispman_element_ = {0}; 36 | 37 | EGL_DISPMANX_WINDOW_T native_window_ = {}; 38 | 39 | bool valid_ = false; 40 | 41 | // |FlutterApplication::RenderDelegate| 42 | bool OnApplicationContextMakeCurrent() override; 43 | 44 | // |FlutterApplication::RenderDelegate| 45 | bool OnApplicationContextClearCurrent() override; 46 | 47 | // |FlutterApplication::RenderDelegate| 48 | bool OnApplicationPresent() override; 49 | 50 | // |FlutterApplication::RenderDelegate| 51 | uint32_t OnApplicationGetOnscreenFBO() override; 52 | 53 | // |FlutterApplication::RenderDelegate| 54 | void *GetProcAddress(const char *) override; 55 | 56 | FLWAY_DISALLOW_COPY_AND_ASSIGN(PiDisplay); 57 | }; 58 | 59 | } // namespace flutter 60 | -------------------------------------------------------------------------------- /flutter/utils.cc: -------------------------------------------------------------------------------- 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 "utils.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace flutter { 12 | 13 | static std::string GetExecutablePath() { 14 | char executable_path[1024] = {0}; 15 | std::stringstream stream; 16 | stream << "/proc/" << getpid() << "/exe"; 17 | auto path = stream.str(); 18 | auto executable_path_size = 19 | ::readlink(path.c_str(), executable_path, sizeof(executable_path)); 20 | if (executable_path_size <= 0) { 21 | return ""; 22 | } 23 | return std::string{executable_path, 24 | static_cast(executable_path_size)}; 25 | } 26 | 27 | std::string GetExecutableName() { 28 | auto path_string = GetExecutablePath(); 29 | auto found = path_string.find_last_of('/'); 30 | if (found == std::string::npos) { 31 | return ""; 32 | } 33 | return path_string.substr(found + 1); 34 | } 35 | 36 | std::string GetExecutableDirectory() { 37 | auto path_string = GetExecutablePath(); 38 | auto found = path_string.find_last_of('/'); 39 | if (found == std::string::npos) { 40 | return ""; 41 | } 42 | return path_string.substr(0, found + 1); 43 | } 44 | 45 | bool FileExistsAtPath(const std::string& path) { 46 | return ::access(path.c_str(), R_OK) == 0; 47 | } 48 | 49 | bool FlutterAssetBundleIsValid(const std::string& bundle_path) { 50 | if (!FileExistsAtPath(bundle_path)) { 51 | FLWAY_ERROR << "Bundle directory does not exist." << std::endl; 52 | return false; 53 | } 54 | 55 | if (!FileExistsAtPath(bundle_path + std::string{"/kernel_blob.bin"})) { 56 | FLWAY_ERROR << "Kernel blob does not exist." << std::endl; 57 | return false; 58 | } 59 | 60 | return true; 61 | } 62 | 63 | } // namespace flutter 64 | -------------------------------------------------------------------------------- /flutter/utils.h: -------------------------------------------------------------------------------- 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 | #pragma once 6 | 7 | #include "macros.h" 8 | 9 | namespace flutter { 10 | 11 | std::string GetExecutableName(); 12 | 13 | std::string GetExecutableDirectory(); 14 | 15 | bool FileExistsAtPath(const std::string& path); 16 | 17 | bool FlutterAssetBundleIsValid(const std::string& bundle_path); 18 | 19 | } // namespace flutter 20 | -------------------------------------------------------------------------------- /tools/setup_gn.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | SDK_DIR="${SCRIPT_DIR}/../sdk" 7 | OUT_DIR="${SCRIPT_DIR}/../out" 8 | ARGS_GN="${OUT_DIR}/args.gn" 9 | 10 | mkdir -p "${OUT_DIR}" 11 | rm -f "${ARGS_GN}" 12 | 13 | echo "toolchain_path = \"${SDK_DIR}/toolchain\"" > "${ARGS_GN}" 14 | echo "sysroot_path = \"${SDK_DIR}/sysroot\"" >> "${ARGS_GN}" 15 | echo "target_triple = \"arm-linux-gnueabihf\"" >> "${ARGS_GN}" 16 | echo "extra_system_include_dirs = [\"/opt/vc/include\"]" >> "${ARGS_GN}" 17 | echo "extra_system_lib_dirs = [\"/opt/vc/lib\"]" >> "${ARGS_GN}" 18 | 19 | "${SDK_DIR}/gn" gen "${OUT_DIR}" 20 | -------------------------------------------------------------------------------- /tools/setup_sdk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | ReportError() { 6 | echo "$@" 1>&2; 7 | } 8 | 9 | TOOCHAIN_NAME="" 10 | GN_PLATFORM_NAME="" 11 | 12 | if [ "$(uname)" == "Darwin" ]; then 13 | TOOLCHAIN_NAME="darwin-x64" 14 | GN_PLATFORM_NAME="mac" 15 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then 16 | TOOLCHAIN_NAME="linux-x64" 17 | GN_PLATFORM_NAME="linux64" 18 | else 19 | ReportError "This platform is not supported. Supported platforms are Darwin or Linux." 20 | exit -1 21 | fi 22 | 23 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 24 | SDK_DIR="${SCRIPT_DIR}/../sdk/" 25 | 26 | rm -rf "${SDK_DIR}" 27 | mkdir -p "${SDK_DIR}" 28 | 29 | echo "Settng up Toolchain..." 30 | wget -O "${SDK_DIR}/toolchain.tar.gz" "https://public.chinmaygarde.com/xcompile/toolchain/${TOOLCHAIN_NAME}.tar.gz" 31 | tar -xzvf "${SDK_DIR}/toolchain.tar.gz" -C "${SDK_DIR}" 32 | rm "${SDK_DIR}/toolchain.tar.gz" 33 | mv "${SDK_DIR}/${TOOLCHAIN_NAME}" "${SDK_DIR}/toolchain" 34 | 35 | echo "Setting up Sysroot..." 36 | wget -O "${SDK_DIR}/sysroot.tar.gz" "https://public.chinmaygarde.com/xcompile/sysroot.tar.gz" 37 | tar -xzvf "${SDK_DIR}/sysroot.tar.gz" -C "${SDK_DIR}" 38 | rm "${SDK_DIR}/sysroot.tar.gz" 39 | 40 | echo "Setting up GN..." 41 | wget -O "${SDK_DIR}/gn" "https://storage.googleapis.com/fuchsia-build/fuchsia/gn/${GN_PLATFORM_NAME}/216b1a3e072f9e531abcf79600fa52fd729b1262" 42 | chmod +x "${SDK_DIR}/gn" 43 | --------------------------------------------------------------------------------