はじめに
Slintで組み込み向けのユーザーインターフェースを開発する際、リソースに制約のあるデバイスではパフォーマンス最適化が重要になります。
最近、Toradex の Verdin AM62 Dual 1GB ET (GPUなしのSoC)向けに Slint の公式のデモの Home Automation を改善していた際、重大な課題に直面しました:GPUアクセラレーションでは完璧に動作していたアニメーションが、CPUのみのソフトウェアレンダラで実行するとパフォーマンスが劣化してしまうのです。
この記事では、この課題にどのように対処したかを探り、Slint の Issue #348(Animate bindings are not reactive and can't reference property set or initialized after)に対する実用的な回避策を提供します。
Issue #348 - アニメーション期間の動的更新ができない
Slint のアニメーションシステムには現時点では重要な制限があります:アニメーションの期間(duration)は初期化時に一度だけ評価され、その後プロパティバインディングで値が更新されても反映されません。
例えば、以下のようなコードを書いても期待通りに動作しません:
// ❌ アニメーション期間が動的に変わることを期待しているが、実際は初期値のみが使われる
animate opacity {
duration: some-dynamic-property * 40ms; // 初期値のみ評価、後で値が変わらない
easing: ease-in-out-sine;
}
これはランタイムでアニメーション期間を変更したい、またはアニメーション自体を条件付きで有効/無効にしたい場合に問題になります。
具体的には、GPU非搭載デバイスで、アニメーションを無効化するために、上記のようなプロパティバインディングで duration
を 0ms
にして無効化できないということになります。
これにより、ソフトウェアレンダリングの際に以下のような問題が生じます。
- アニメーションによるCPU負荷が高い
- フレームレートの低下を引き起こす
- ユーザー体験の劣化につながる
解決策:2つの回避パターン
パターン1: Statesを使用した条件付きアニメーション
statesを使用して、同じプロパティに対して異なるアニメーション設定を持つ複数のステートを定義します。
実装例:カメラビュー切り替え
前述の現在の制限により、以下のコードは意図したとおりには動作しません。
opacity: camera-view == CameraView.back ? 1 : 0
animate opacity { duration: AppState.graphics-accelerator-available ? 300ms : 0ms; }
この場合、以下のように state と transition で書き下すことで実現が可能です。
states [
// GPU ありの場合にはトランジションのアニメーションを設定する
back-with-animation when camera-view == CameraView.back && AppState.graphics-accelerator-available: {
opacity: 1;
in-out {
animate opacity { duration: 300ms; }
}
}
front-with-animation when camera-view == CameraView.front && AppState.graphics-accelerator-available: {
opacity: 0;
in-out {
animate opacity { duration: 300ms; }
}
}
// GPU なしの場合にはトランジションのアニメーションを設定しない
back-without-animation when camera-view == CameraView.back && !AppState.graphics-accelerator-available: {
opacity: 1;
}
front-without-animation when camera-view == CameraView.front && !AppState.graphics-accelerator-available: {
opacity: 0;
}
]
パターン2: If条件によるコンポーネント切り替え
slint は if 文によるコンポーネントの生成に対応しているため、GPU の有無に応じてコンポーネントを切り替えることも可能です。
実装例:ページボタンハイライター
// GPU使用可能時:アニメーション付き
if AppState.graphics-accelerator-available: PageButtonHighlighter {
x: home.x + (tabs.spacing + self.width) * AppState.target-page;
animate x {
duration: Animation.transition-duration;
easing: ease-in-out-sine;
}
width: AppState.orientation == Orientation.landscape ? 120px : 140px;
height: 100%;
}
// GPU使用不可時:アニメーションなし
if !AppState.graphics-accelerator-available : PageButtonHighlighter {
x: home.x + (tabs.spacing + self.width) * AppState.target-page;
// animate ブロックなし
width: AppState.orientation == Orientation.landscape ? 120px : 140px;
height: 100%;
}
おわりに
Slint の Issue #348 は、アニメーション期間が初期化時にのみ評価されるという制限です。しかし、以下のパターンを使うことで効果的に回避できます:
- Statesパターン: 同じ要素に対して条件付きで異なるアニメーションを適用
- If条件パターン: コンポーネント自体をアニメーションあり/なしで切り替え
このアプローチにより1つのコードベースで以下を実現することが可能です:
- GPU対応デバイス:滑らかで美しいアニメーション
- CPU専用デバイス:即応性の高いレスポンシブなUI
- 両方で最適なユーザー体験
近い将来に Slint 自体でこの問題が解決され、duration
の動的更新が可能になると嬉しいですね。