Flutter 3.47解説 — Impellerがデスクトップ既定に、ただし「Vulkan移行」ではない

はじめに

Flutter 3.47 がリリースされ、Impeller が macOS・Windows・Linux すべてで既定のレンダラに昇格した。iOS と Android では 3.27 の時点で既定になっていたので、これで Web を除く全プラットフォームが Impeller に揃ったことになる。

リリース直後の r/FlutterDev では「Windows のテキスト描画が信じられないほど良くなった」という実機報告が上位に並んだ。ただ、公式ブログの説明を読んで「デスクトップがついに Vulkan で動くようになった」と受け取ると、実装とはズレる。本記事では、テキスト品質が改善した具体的な機構、公式の説明と実際に出荷されたバックエンドの食い違い、そしてアップグレードを実際に止めにくる3つの破壊的変更を順に整理する。

Windowsのテキストが良くなったのは「Impellerだから」ではない

まず押さえておきたいのは、テキスト品質の改善は Impeller への切り替えそのものの副産物ではなく、切り替えに合わせて明示的に有効化された2つの機構によるものだという点だ。

1つ目が SDF(Signed Distance Field)レンダリングである。グリフをラスタライズ済みのビットマップとして持つのではなく、輪郭からの符号付き距離を格納したテクスチャとして扱い、シェーダ側で輪郭を復元する方式だ。拡大縮小してもエッジが崩れず、ベクター曲線がきれいに出る。Flutter はこれをモバイルには入れず、デスクトップにだけ有効化している。デスクトップ側の GPU 演算能力に余裕があることが理由として挙げられている。

実装は素朴で、Windows エンベッダは Impeller が有効なときに --impeller-use-sdfs=true をコマンドラインスイッチへ自動注入する(#187877。ユーザーが明示的に true/false を指定していない場合のみ)。同 PR では hello_world_windows_impeller.dart という Devicelab タスクも追加され、この構成が壊れていないことを継続的に検証する体制が入っている。Linux 側の SDF 有効化はこれとは別の PR(#186909)で入っている。

2つ目がガンマ補正だ。これはプラットフォームごとに独立した PR として入っており、Windows のテキストに対するガンマ補正が #187871、Linux のグリフに対するガンマ補正が #187122 である。アンチエイリアスされたテキストが「にじんで見える」現象の典型的な原因は、誤ったガンマ空間でのブレンドだ。線形空間で処理すべき合成を sRGB のまま行うと、細い縦線が痩せたり太ったりして輪郭が濁る。Windows で体感差が大きいのは、SDF 単体ではなくこのガンマ補正と組み合わさった結果と読むのが妥当だろう。

macOS 側でも、ライト/ダークでグリフを分けることでストローク太さの不整合を解消する修正(#186074)が入っており、3.47 のテキスト周りは「Impeller 移行のついで」ではなく独立した改善群として読むのが正しい。

公式ブログは「Vulkan」と書いているが、出荷されたのはOpenGL ES

ここが今回いちばん誤解を招きやすい部分だ。

公式ブログには、こう書かれている。

By targeting modern hardware APIs (like Metal on macOS and Vulkan on Windows and Linux), Impeller compiles a fixed set of shaders at build time rather than compiling them dynamically at runtime.

(モダンなハードウェアAPI——macOS では Metal、Windows と Linux では Vulkan——をターゲットにすることで、Impeller はシェーダをランタイムで動的にコンパイルするのではなく、固定のシェーダ集合をビルド時にコンパイルする)

この一文だけを読むと、3.47 のデスクトップ Impeller は Vulkan バックエンドで動いていると理解してしまう。だが 3.47.0 タグのソースを見ると、そうはなっていない。

Windows エンベッダの flutter_windows_engine.cc には // using OpenGL (via ANGLE) というコメントとともに config.type = kOpenGL; があり、Impeller が有効なときも GetOpenGLRendererConfig() を通る。Vulkan 経路そのものが存在しない。ディレクトリ構成も裏付けになっていて、Windows 側のコンポジタ実装は compositor_opengl.cc と compositor_software.cc に egl/ を加えたものだけ、Linux 側も fl_compositor_opengl.cc / fl_compositor_software.cc / fl_opengl_manager.cc のみで、Vulkan の実装ファイルは両プラットフォームともない。

さらに決定的なのが、PR #187877 が追加した Devicelab タスクの中身だ。このテストは、アプリの標準出力に Using the Impeller rendering backend (OpenGLESSDF). が現れることをアサートしている。つまり Flutter 自身の CI が「Windows の Impeller は OpenGL ES である」ことを検証している。

Vulkan 側はどうなっているか。「Impeller Vulkan rendering backend for Linux and Windows desktops」という設計ドキュメントは 2026年3月11日に issue #183495 として起票され、P2 / design doc / c: proposal のラベルが付いたまま現在も open だ。この設計ドキュメント自身が、デスクトップの現状を「Skia + OpenGL(Windows では ANGLE)」と記述し、ランタイムのシェーダバリアント生成に由来するファーストフレームのジャンクを解決するために、Vulkan バックエンドとコンポジタごとのプレゼンテーション層を新設したいと提案している。実装 PR #183382 は 2026年7月16日にマージされないままクローズされ(作者コメントは「Closing this one; review should continue on the new PRs.」)、分割後の #189580 / #189584 / #189586 はいずれも現時点で open のままだ。しかも Windows 分の #189586 は本文で「opt-in な Vulkan レンダリングパスを追加する」と明記している。3.47.0 のタグが打たれたのは 2026年8月11日なので、日付の上でも Vulkan が 3.47 に入る余地はなかった。

つまり 3.47 で起きたのは、レンダラの実装が Skia から Impeller に入れ替わったことであって、グラフィックスAPIが OpenGL 系から Vulkan に移ったことではない。ブログの記述は Impeller というプロジェクト全体の設計思想を語ったもので、デスクトップで今動いている構成の説明としては先走っている。

この区別は実務上どうでもいい話ではない。Impeller の最大の売りは「シェーダをビルド時にコンパイルしておくことでランタイムのシェーダコンパイルジャンクを消す」という点だが、デスクトップでその恩恵がどこまで出るかはバックエンドの性質に左右される。「Vulkan に載ったから速くなったはず」という前提で性能を見積もると外す。3.47 のデスクトップで確実に手に入るのは、レンダリングパイプラインの統一とテキスト品質の改善であり、Vulkan 由来の性能特性はこれからの話だ。

ついでに言えば、公式が 3.47 の破壊的変更として索引しているのは2件だけで、そのうち1件が「Impeller’s OpenGL ES backend now stores render-to-texture content top-down」である。デスクトップで効いてくるのが GLES バックエンドだという事実は、破壊的変更の側からも裏が取れる。

なお、Impeller のオプトアウトは全プラットフォームで健在で、Windows なら windows\runner\main.cpp で project.set_impeller_switch(flutter::ImpellerSwitch::Disabled)、Linux なら fl_dart_project_set_enable_impeller(project, FALSE)、開発中は flutter run --no-enable-impeller が使える。ただし公式は「フォールバックは将来のリリースで削除されるので、Skia に戻さざるを得ないならバグを報告してほしい」と明言している。戻せるうちに、戻さずに済ませる作業をしておくのが正しい向き合い方になる。

アップグレードで踏む段差

3.47 は変更の質が高い一方で、上げる前に潰しておくべき段差がいくつかある。

1. macOS の最低バージョンが 10.15 → 12.0(#188520)。Xcode 27 対応のための引き上げで、あわせて iOS 14 / 15 向けの availability チェックがエンベッダから削除された。自分の開発機だけでなく、CI ランナーの macOS イメージとサポート表明の見直しが先に必要になる。

2. Dart SDK の最低要件が 3.11 系へ(#188357)。3.47.0 の packages/flutter/pubspec.yaml の制約は sdk: ^3.11.0-0 で、3.11.0 のプレリリースも許容される形になっている。依存パッケージ側の SDK 制約も再適用されるため、sdk: 制約が古いまま放置されているプロジェクトは解決に失敗する。

3. Android テンプレートの AGP 9 化(#185953、#188543)。ここは誤解されやすいので正確に書く。AGP 9.0 では Kotlin Gradle Plugin(KGP)の適用サポートが削除され、AGP 組み込みの Kotlin(built-in Kotlin)が既定になる。公式ドキュメントも「built-in Kotlin は AGP 9 以降で既定であり、kotlin-android プラグインを使うアプリはビルドに失敗する」と明言している。

ただし、3.47 に上げただけでビルドが壊れるわけではない。 Flutter が新テンプレートの gradle.properties に実際に書き込んでいるのは android.builtInKotlin=false と android.newDsl=false、つまりレガシー挙動へのオプトアウトが既定だ。Flutter 側は「アプリやプラグインの移行状態にかかわらずビルドが成功するよう、既定ではレガシー挙動にしている」と説明している(#183910)。

問題が顕在化するのは android.builtInKotlin=true にオプトインしたときだ。pub.dev 上のプラグインの多くは自身の android/build.gradle で kotlin-android を適用しており、AGP 9 はそれを見つけた時点でコンパイルエラーにする。つまりオプトイン後は、アプリ側の移行が完了していても依存プラグイン1つが未対応なら止まる。移行手順自体は公式に整理されていて、kotlin-android プラグインと kotlinOptions {} ブロックを削除し、代わりにトップレベルの kotlin { compilerOptions { ... } } へ書き換えたうえで android.builtInKotlin を true にする、という流れになる(built-in Kotlin の有効化には Flutter 3.47 以降が必要)。そして KGP を許容するこの互換サポートは「将来の Flutter バージョンで削除される」と予告されているので、逃げ切りは効かない。既定がオフである今のうちに、オプトインして依存プラグインの対応状況を測っておくのが安全だ。

4. Windowing API のリネーム。パラメータ名が preferredSize → size、preferredConstraints → constraints に変わり(#189017)、decorated フラグは Windows では削除、Linux ではオプションとして存置という非対称な扱いになった(#184977)。マルチウィンドウ API はまだ落ち着いていないので、追従するなら破壊的変更が続く前提で組むべきだ。

なお、公式が「3.47 でリリースされた破壊的変更」としてインデックスに載せているのは前述の OpenGL ES render-to-texture の件と semantics の header / headingLevel の2件のみで、ここに挙げた4つはそれとは別枠の変更である。公式の破壊的変更一覧だけを見てアップグレード計画を立てると足をすくわれる、という意味でも押さえておきたい。

デスクトップ実用化に効く地味な追加

破壊的変更の陰に隠れがちだが、デスクトップアプリを本気で作るなら効く追加が2つある。

1つはポップアップウィンドウで、Win32(#184516)と Linux(#185866)の実装が入り、通常ウィンドウとダイアログの sized-to-content にも対応した。コンテキストメニューやユーティリティパレットのように、本体ウィンドウの外にはみ出す UI をネイティブのウィンドウとして出せるようになったということだ。

もう1つが windowHandle の公開(#184662)で、プラットフォームコントローラから HWND / NSWindow / GtkWindow といったネイティブハンドルを取得できる。ドッキング可能なペインのように、Flutter のウィジェット階層だけでは実現できない機能を、ネイティブ API を直接叩いて構築する道が開いた。ただし 3.47.0 のソースを見ると、この API は3プラットフォームとも @internal が付いた実験的な位置づけである。プロダクションで前提にするのはまだ早い。

アクセシビリティ面では、Android の高コントラストと色反転設定が MediaQueryData から自動検出されるようになり(#182263)、フレームワークの semantics role が Android のクラスへマッピングされた(#188037)。Web では CanvasKit / Skwasm の段階的画像ダウンスケーリング(#184741)とフォントフォールバックサービス(#185314)が入り、HTML レンダラの残存参照は完全に削除された。

まとめ

  • Flutter 3.47 で Impeller が macOS / Windows / Linux の既定レンダラになり、Web を除く全プラットフォームが Impeller に統一された
  • Windows のテキスト品質改善の実体は、SDF レンダリングの有効化とガンマ補正という2つの明示的な変更であり、Impeller 切り替えの自動的な副産物ではない
  • 公式ブログは Vulkan に言及しているが、デスクトップで出荷されたのは OpenGL ES バックエンド。Flutter 自身の Devicelab が OpenGLESSDF を検証しており、Vulkan バックエンドは issue #183495 で P2 の設計提案段階にとどまる。性能見積もりの前提にしてはいけない
  • アップグレードの段差は macOS 12.0・Dart 3.11 系・AGP 9・Windowing API のリネーム。ただし built-in Kotlin は既定オフなので、3.47 に上げただけでプラグイン起因のビルド破壊は起きない。壊れるのは android.builtInKotlin=true にオプトインしてから

Impeller のオプトアウトも、AGP 9 のレガシー互換も、いまはまだ残っている。しかしどちらも公式が削除を予告している猶予期間にすぎない。3.47 との正しい付き合い方は、この猶予のあるうちに「戻さずに済む状態」を作っておくことだ。手をつける順番としては、android.builtInKotlin=true を試しに立てて依存プラグインがどれだけ落ちるかを測るところからだろう。既定がオフの今なら、測ってから戻せる。

今日のその他のニュース

GLM-5.3 が公開、HN で本日最多の 949pt — Zhipu AI が GLM-5.3 を発表。コーディング性能でフロンティア級を主張し、「emergent cyber capabilities」という表現でセキュリティ領域の能力向上に自ら言及した点が特徴的。同日に Qwen 3.8 27B(HN 456pt)も公開されている。

Supabase が realtime スキーマへの DDL を全面ブロック — Changelog によると realtime スキーマ内のオブジェクト作成・変更・削除が permission error に。マイグレーションで触っている場合は落ちるため事前確認が要る。realtime.messages への RLS ポリシーは引き続き有効。

V8 の Array.prototype.copyWithin が最大約450倍高速化 — Zenn の貢献記録。要素ごとのループをメモリコピーベースに置き換えたもの。枯れた領域の標準ビルトインにも素朴な実装が残っている実例。

ソース