組込みシステム(Embedded Systems)におけるGUI開発は、長らくQtやGTK、あるいはHTML5ベースのソリューション(Electron等)の間でトレードオフを強いられてきました。ネイティブコードはパフォーマンスに優れますが学習コストが高く、Web技術は生産性が高い反面、リソースの限られたハードウェアではパフォーマンスのボトルネックが発生しがちです。
Flutter Embeddedはこの膠着状態を打破する選択肢として浮上しました。トヨタやBMWが車載インフォテインメントシステム(IVI)にFlutterを採用したのは、単なるUIの美しさではなく、Embedder APIによる移植性とSkia(現在はImpeller)レンダリングエンジンによる描画性能が、厳格なリソース制約を持つ組込み環境に適合したためです。本稿では、モバイルフレームワークとしてのFlutterではなく、Linuxカーネル上で直接動作するGUIランタイムとしてのFlutterのアーキテクチャを解説し、Raspberry Piを用いた実装プロセスを詳述します。
1. Flutter Embeddedのアーキテクチャ解析
FlutterがAndroid/iOS以外のプラットフォームで動作する仕組みを理解するには、そのレイヤー構造を把握する必要があります。Flutterは大きく分けて以下の3層で構成されます。
| レイヤー | 主要言語 | 役割 |
|---|---|---|
| Framework | Dart | Widget、アニメーション、ジェスチャー処理などの高レベルロジック。 |
| Engine | C++ | Skia/Impellerによるレンダリング、Dart VM、テキストレイアウト。 |
| Embedder | C++ / Objective-C / Java | OS固有のエントリーポイント、イベントループ、レンダリングサーフェスの提供。 |
組込み開発において最も重要なのが最下層のEmbedderです。モバイルアプリ開発ではGoogleが提供する標準Embedderを使用しますが、Linux組込み環境では、対象ハードウェア(Raspberry Pi等)のディスプレイサーバ(X11, Wayland)や、グラフィックAPI(OpenGL, Vulkan)に合わせてカスタムEmbedderを利用、あるいは自作する必要があります。
なぜトヨタはFlutterを選んだのか
トヨタのエンジニアリングチームが公開している技術資料によると、採用の決め手はAOT(Ahead-of-Time)コンパイルによる起動速度と、メモリ管理の予測可能性です。JIT(Just-in-Time)コンパイルに依存するJavaScriptベースのフレームワークとは異なり、Flutterはリリースビルド時にネイティブマシンコードへコンパイルされるため、低スペックなCPUでも安定したフレームレートを維持できます。
2. Raspberry Piへの実装戦略:flutter-pi
Raspberry PiでFlutterを動作させるアプローチには主に以下の2つがあります。
- sony/flutter-elinux: WaylandまたはX11コンポジタ上で動作するEmbedder。デスクトップライクな利用に適しています。
- ardera/flutter-pi: X11やWaylandを必要とせず、DRM (Direct Rendering Manager) と KMS (Kernel Mode Setting) を介して直接描画する軽量Embedder。
本稿では、「OSのような」キオスクモード体験を構築するため、オーバーヘッドの少ないflutter-piを採用します。これはウィンドウマネージャを経由せず、Flutterがディスプレイ出力を独占する構成です。
環境セットアップとビルドプロセス
まず、Raspberry Pi OS(64-bit推奨)に必要なライブラリをインストールします。これらはEmbedderがグラフィックスハードウェアと通信するために不可欠です。
# 必要な依存関係のインストール
sudo apt update && sudo apt install -y \
cmake \
libgl1-mesa-dev \
libgles2-mesa-dev \
libegl1-mesa-dev \
libdrm-dev \
libgbm-dev \
fontconfig \
libsystemd-dev \
libinput-dev \
libudev-dev \
libxkbcommon-dev
AOTバイナリの生成
開発機(PC/Mac)側でFlutterプロジェクトをビルドします。重要なのは、ターゲットアーキテクチャ(ARM64)に合わせたスナップショットを作成することです。
# 開発機でのビルドコマンド
# --release: AOTコンパイルを実行
# target-platform: Raspberry Pi 4 (64bit)の場合はlinux-arm64を指定
flutter build bundle
生成されたbuild/flutter_assetsディレクトリをRaspberry Piに転送します。scpやrsyncを使用してください。
ランタイムの実行
Raspberry Pi側でflutter-piコマンドを使用し、アセットをロードします。この際、ディスプレイサーバーが起動していない(CUIログイン状態)であることを確認してください。
# Raspberry Pi側での実行
# -r: 画面の向き(回転)指定
# --release: リリースモードで実行
flutter-pi --release ./flutter_assets
video および render グループに所属しているか確認してください。
sudo usermod -aG video,render $USER
3. レンダリングエンジンの最適化とImpeller
組込み環境でFlutterを使用する際、最も頻繁に直面する課題が「シェーダーコンパイルによるジャンク(カクつき)」です。従来のSkiaエンジンでは、アプリ実行中にシェーダーをコンパイルするため、初回のアニメーション実行時にフレームドロップが発生することがありました。
Impellerへの移行
Flutterチームは現在、Skiaを置き換える新しいレンダリングエンジンImpellerを推進しています。Impellerはシェーダーをビルド時に事前コンパイル(AOT)するため、ランタイムでのコンパイル負荷が消失し、予測可能なパフォーマンスを提供します。
4. パフォーマンスチューニングと監視
Raspberry Piのようなリソース制限のある環境では、メモリリークやCPUスパイクが致命的になります。開発機側でDevToolsを使用し、以下のメトリクスを監視します。
- Raster Thread Time: GPUへのコマンド送信にかかる時間。16ms(60fpsの場合)を超えると画面のカクつきが発生します。
- UI Thread Time: Dartコードの実行時間。重い計算処理はIsolate(別スレッド)に逃がす必要があります。
# Compute関数を使用した並列処理の例
import 'package:flutter/foundation.dart';
Future<void> heavyDataProcessing() async {
// メインUIスレッドをブロックしないよう、別Isolateで実行
final result = await compute(expensiveFunction, data);
updateState(result);
}
結論:トレードオフと展望
Flutterを組込みOSのUIレイヤーとして使用することは、圧倒的な開発生産性とクロスプラットフォームの恩恵をもたらしますが、バイナリサイズ(最小でも数MB〜)やメモリフットプリントに関しては、生のC/C++実装と比較してトレードオフが存在します。
しかし、ハードウェアの性能向上とFlutterエンジンの軽量化(Impellerの導入等)により、このギャップは急速に縮まっています。Raspberry Piでのプロトタイピングから始め、カスタムEmbedderを用いた専用ハードウェアへの展開は、現代のIoT開発において極めて合理的な選択肢と言えるでしょう。
Post a Comment