Flutter Mobile Development · Lekcja

Rozgrzewanie shaderów i migracja do Impellera

Eliminuj zacięcia przy pierwszym uruchomieniu, wstępnie kompilując shadery i przyjmując renderer Impeller

Lekcja 4 z 413 kroki

Rozgrzewanie shaderów i migracja do Impellera to bezpłatna lekcja Flutter Mobile Development na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Flutter Mobile Development, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Flutter Mobile Development zawiera 4 lekcji w sumie.

Dlaczego zacinanie występuje przy pierwszym uruchomieniu

Gdy aplikacja Flutter po raz pierwszy rysuje konkretny efekt, backend GPU musi skompilować bazowy program shaderu na urządzeniu. W starszym backendzie Skia kompilacja ta odbywa się leniwie, dokładnie w środku klatki, która jej potrzebuje.

  • Kompilacja shaderu może trwać kilkadziesiąt milisekund.
  • Przekracza to budżet 16 ms klatki renderowanej z częstotliwością 60 kl./s, powodując widoczne przycięcie nazywane zacinaniem shaderu.
  • Problem jest największy przy pierwszym uruchomieniu, ponieważ nic nie jest jeszcze zapisane w pamięci podręcznej.

Najczęstszymi przyczynami są animacje, przejścia między stronami i rozmycia BackdropFilter.

Gdzie znika czas

Klatka zacinająca się z powodu kompilacji shaderu jest wyraźnie widoczna w widoku Performance narzędzia DevTools jako wysoki słupek wątku rasteryzacji z zdarzeniem ShaderCompilation.

Aby wiarygodnie odtworzyć problem i go zmierzyć, należy uruchomić aplikację w profile mode (nigdy w trybie debug, który jest znacznie wolniejszy i może wprowadzać w błąd):

  • Tryb profile zapewnia wydajność zbliżoną do wersji produkcyjnej oraz mechanizmy śledzenia.
  • Oś czasu DevTools oznacza zdarzenia kompilacji shaderów, dzięki czemu można potwierdzić przyczynę źródłową przed rozpoczęciem optymalizacji.
// Run the app in profile mode to capture realistic frame timings.
// flutter run --profile

// Then open DevTools > Performance and look for
// 'ShaderCompilation' events on the raster thread.
// flutter run --profile --trace-skia

Strategia rozgrzewania Skia

W starszym backendzie Skia klasycznym rozwiązaniem jest rozgrzewanie shaderów: należy zebrać używane przez aplikację shadery w pakiet, a następnie wstępnie skompilować je podczas uruchamiania, zanim użytkownik zacznie korzystać z aplikacji.

Flutter generuje ten pakiet za pomocą flagi --cache-sksl, która rejestruje programy SkSL (Skia Shader Language) podczas korzystania z aplikacji:

// 1. Run in profile mode, capturing SkSL while you navigate every screen
//    and trigger every animation that might cause jank.
// flutter run --profile --cache-sksl --purge-persistent-cache

// 2. In the running app, press 'M' in the terminal to write the
//    captured shaders to a JSON file, e.g. flutter_01.sksl.json

Tworzenie pakietu przechwyconych shaderów

Gdy masz już przechwycony plik .sksl.json, dołącz go do kompilacji wydaniowej. Flutter wstępnie kompiluje te shadery podczas fazy rozgrzewania silnika, dzięki czemu są gotowe przed wyświetleniem pierwszej klatki użytkownikowi.

  • Przechwytuj shadery na fizycznym urządzeniu podobnym do docelowego sprzętu.
  • Wykonuj przechwytywanie ponownie za każdym razem, gdy interfejs znacząco się zmieni.
// Bundle the captured SkSL into a release build:
// flutter build apk --bundle-sksl-path flutter_01.sksl.json
// flutter build ios --bundle-sksl-path flutter_01.sksl.json

// The engine warms up these shaders at launch,
// eliminating compile stalls during animations.

Dlaczego rozgrzewanie Skia to rozwiązanie tymczasowe

Rozgrzewanie SkSL działa, ale ma istotne wady, które skłoniły zespół do opracowania głębszego rozwiązania:

  • Przechwycone dane są zależne od urządzenia i sterownika; pakiet z jednego układu GPU może nie obejmować innego.
  • Po zmianach interfejsu trzeba pamiętać o ponownym przechwyceniu danych, inaczej zacinanie po cichu powróci.
  • Obejmuje tylko te shadery, które zostały użyte podczas przechwytywania.

Trwałą odpowiedzią zespołu Fluttera jest nowy silnik renderowania, który w ogóle nie kompiluje shaderów w czasie działania: Impeller.

Jak Impeller eliminuje problem

Impeller wstępnie kompiluje niewielki, stały zestaw shaderów podczas kompilowania silnika, a nie w czasie działania. Zamiast generować dowolne shadery dla każdego wywołania rysowania, składa efekty ze znanych wcześniej programów.

  • Brak kompilowania shaderów w czasie działania oznacza brak zacinania przy pierwszym uruchomieniu z założenia.
  • Na iOS używa Metal, a na nowoczesnym Androidzie Vulkan.
  • Ponieważ shadery są znane z wyprzedzeniem, rozgrzewanie za pomocą --cache-sksl jest zbędne i nieobsługiwane przez Impeller.

Domyślny status Impellera

Impeller jest obecnie domyślnym rendererem na iOS oraz na nowoczesnym Androidzie (na urządzeniach obsługujących Vulkan), zgodnie z najnowszymi stabilnymi wydaniami Fluttera. Na starszym sprzęcie z Androidem bez obsługi Vulkan silnik automatycznie korzysta z backendu OpenGL.

Większość aplikacji uzyskuje te korzyści bez zmian w kodzie. Migracja polega na zweryfikowaniu poprawności wizualnej i obsłudze kilku przypadków brzegowych, w których Impeller i Skia zachowują się inaczej.

Przełączanie Impellera dla poszczególnych platform

Impellerem steruje się za pomocą natywnych manifestów platformy, a nie kodu Dart. Dzięki temu można go włączyć, wyłączyć lub porównać z Skią podczas testów migracji.

Na iOS ustaw flagę w pliku Info.plist, a na Androidzie w pliku AndroidManifest.xml:

<!-- ios/Runner/Info.plist -->
<key>FLTEnableImpeller</key>
<true/>

<!-- android/app/src/main/AndroidManifest.xml (inside <application>) -->
<meta-data
    android:name="io.flutter.embedding.android.EnableImpeller"
    android:value="true" />

Własne shadery nadal wymagają rozgrzewania

Jeśli dostarczają Państwo własne shadery fragmentów GLSL za pomocą FragmentProgram, stanowią one Państwa kod i nie należą do wbudowanego zestawu Impellera. Kompilowanie lub wczytywanie ich na żądanie nadal może zatrzymać renderowanie klatki.

Rozwiązaniem jest wczytanie ich i rozgrzanie podczas uruchamiania aplikacji, zanim zostaną użyte po raz pierwszy w animacji:

import 'package:flutter/material.dart';

class ShaderCache {
  static FragmentProgram? ripple;

  // Call during startup so the program is ready before first paint.
  static Future<void> warmUp() async {
    ripple = await FragmentProgram.fromAsset('shaders/ripple.frag');
  }
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await ShaderCache.warmUp();
  runApp(const MyApp());
}

Wstępne renderowanie kosztownych efektów

Nawet po wstępnym skompilowaniu shaderów pierwsze zbudowanie kosztownego widgetu może nadal trwać dłużej niż kolejne. Często stosowaną techniką jest renderowanie ciężkiego efektu poza ekranem podczas ekranu powitalnego lub klatki rozgrzewającej, aby wykonać tę pracę, zanim użytkownik do niego przejdzie.

Po zatwierdzeniu pierwszej klatki można uruchomić jednorazowe renderowanie rozgrzewające trwające jedną klatkę:

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void scheduleWarmUp(VoidCallback warmUpExpensiveEffects) {
  // Runs once after the first frame is rendered,
  // so warm-up work does not block startup paint.
  SchedulerBinding.instance.addPostFrameCallback((_) {
    warmUpExpensiveEffects();
  });
}

Mierzenie efektu

Zawsze potwierdzaj poprawę danymi, a nie przeczuciami. Porównaj najgorszy czas rasteryzacji klatki przed zmianą i po niej, na rzeczywistym urządzeniu, w trybie profile.

Na podstawie przechwyconych czasów renderowania klatek można obliczyć proste statystyki i sprawdzić, czy klatka z 99. percentyla mieści się teraz w budżecie:

void main() {
  // Raster times in milliseconds captured before the warm-up fix.
  final frames = <double>[8.1, 7.9, 42.6, 8.0, 9.3, 8.2, 7.7];

  frames.sort();
  final worst = frames.last;
  final p50 = frames[frames.length ~/ 2];
  const budget = 16.0; // 60fps frame budget

  print('p50: ${p50}ms  worst: ${worst}ms');
  print(worst > budget
      ? 'Jank present: worst frame exceeds ${budget}ms'
      : 'All frames within budget');
}

Szybkie sprawdzenie

Migrują Państwo aplikację na poziomie C1 ze Skia do Impellera, aby naprawić zacinanie shaderów przy pierwszym uruchomieniu. Co stanie się z istniejącym pakietem rozgrzewającym SkSL i dlaczego?

Podsumowanie

Wiedzą już Państwo, jak wyeliminować zacinanie shaderów przy pierwszym uruchomieniu we Flutterze:

  • Diagnozowanie zatrzymań spowodowanych kompilacją shaderów w widoku Performance narzędzia DevTools, z użyciem trybu profile.
  • Rozgrzewanie Skia za pomocą --cache-sksl i --bundle-sksl-path wstępnie kompiluje przechwycone shadery SkSL, ale jest zależne od urządzenia i kruche.
  • Impeller to trwałe rozwiązanie: wstępnie kompiluje stały zestaw shaderów podczas kompilowania, dzięki czemu nie ma kompilowania w czasie działania ani zacinania shaderów. Jest domyślny na iOS oraz na nowoczesnym Androidzie (z Vulkanem).
  • Impellera można przełączać za pomocą Info.plist i AndroidManifest.xml; po migracji należy usunąć pakiet SkSL.
  • Własne shadery FragmentProgram nadal wymagają jawnego rozgrzania podczas uruchamiania.
  • Zawsze mierz czas rasteryzacji najgorszej klatki na rzeczywistym urządzeniu, aby potwierdzić poprawę.
Bezpłatny start

Ucz się Dart dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
22
Lekcje
88

Często zadawane pytania

Czy lekcja „Rozgrzewanie shaderów i migracja do Impellera” jest bezpłatna?

Tak — pełny tekst „Rozgrzewanie shaderów i migracja do Impellera” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Flutter Mobile Development, przejdź na CoddyKit PRO. Kurs Flutter Mobile Development zawiera 4 lekcji w sumie.

Co nauczysz się w „Rozgrzewanie shaderów i migracja do Impellera”?

Eliminuj zacięcia przy pierwszym uruchomieniu, wstępnie kompilując shadery i przyjmując renderer Impeller Ćwiczysz Flutter Mobile Development z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Flutter Mobile Development?

Nie wymagamy żadnego doświadczenia. Flutter Mobile Development w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Rozgrzewanie shaderów i migracja do Impellera”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Flutter Mobile Development?

Tak. Każda lekcja Flutter Mobile Development zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Trzy drzewa: Widget, Element i RenderObject
  2. Profilowanie zacięć za pomocą osi czasu DevTools
  3. RepaintBoundary, widgety const i ograniczanie przebudowy
  4. Rozgrzewanie shaderów i migracja do Impellera
← Powrót do Flutter Mobile Development