Raspberry PiでのFlutter Embedded環境構築と最適化

込みシステム(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つがあります。

  1. sony/flutter-elinux: WaylandまたはX11コンポジタ上で動作するEmbedder。デスクトップライクな利用に適しています。
  2. ardera/flutter-pi: X11やWaylandを必要とせず、DRM (Direct Rendering Manager)KMS (Kernel Mode Setting) を介して直接描画する軽量Embedder。

本稿では、「OSのような」キオスクモード体験を構築するため、オーバーヘッドの少ないflutter-piを採用します。これはウィンドウマネージャを経由せず、Flutterがディスプレイ出力を独占する構成です。

Dependencies Note: flutter-piはOpenGL ES 2.0を使用します。Raspberry Pi 4以降ではGPUドライバ(VC4)が適切に設定されている必要があります。

環境セットアップとビルドプロセス

まず、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に転送します。scprsyncを使用してください。

ランタイムの実行

Raspberry Pi側でflutter-piコマンドを使用し、アセットをロードします。この際、ディスプレイサーバーが起動していない(CUIログイン状態)であることを確認してください。

# Raspberry Pi側での実行
# -r: 画面の向き(回転)指定
# --release: リリースモードで実行

flutter-pi --release ./flutter_assets
Troubleshooting: "Permission denied" エラーが /dev/dri/card0 に対して発生する場合、現在のユーザーが video および render グループに所属しているか確認してください。 sudo usermod -aG video,render $USER

3. レンダリングエンジンの最適化とImpeller

組込み環境でFlutterを使用する際、最も頻繁に直面する課題が「シェーダーコンパイルによるジャンク(カクつき)」です。従来のSkiaエンジンでは、アプリ実行中にシェーダーをコンパイルするため、初回のアニメーション実行時にフレームドロップが発生することがありました。

Impellerへの移行

Flutterチームは現在、Skiaを置き換える新しいレンダリングエンジンImpellerを推進しています。Impellerはシェーダーをビルド時に事前コンパイル(AOT)するため、ランタイムでのコンパイル負荷が消失し、予測可能なパフォーマンスを提供します。

Best Practice: 現在の組込み向けEmbedderではImpellerのサポート状況が異なりますが、可能な限りVulkanバックエンドとImpellerを有効化する方向で設計を進めるべきです。これにより、特に起動直後のUI応答性が大幅に向上します。

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