[Разработка под Android, Dart, Разработка под Linux, Производство и разработка электроники, Flutter] Как портировать SDK Flutter на ТВ-приставку для разработки и запуска приложений Android TV

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
15-Апр-2021 18:31


Недавно мы успешно портировали фреймворк Flutter на ТВ-приставку c открытой программной платформой RDK. В этой статье расскажем о трудностях, с которыми пришлось столкнуться, и предложим решения для успешного запуска и повышения производительности. Учитывая, что программный стек RDK или Reference Design Kit сейчас активно используется для разработки OTT-приложений, голосового управления приставками и других продвинутых функций для «видео по запросу» (VoD), мы хотели разобраться, сможет ли Flutter работать на ТВ-приставке. Оказалось, что да, но, как это обычно бывает, есть нюансы. Далее мы по шагам распишем процесс портирования и запуска Flutter на встраиваемых Linux-платформах и разберемся, как этот SDK с открытым исходным кодом от Google чувствует себя на «железе» с ограниченными ресурсами и ARM-процессорами.Но прежде чем переходить непосредственно к Flutter и его преимуществам скажем пару слов об исходном решении, которое было задействовано на ТВ-приставке. На плате работала связка «набор библиотек EFL + протокол Wayland», а рисование примитивов было реализовано из node.js на основе плагинного нативного модуля. Это решение неплохо себя показало с точки зрения производительности при отображении кадров, однако сам EFL — отнюдь не самый новый фреймворк для отрисовки. А в режиме выполнения node.js со своим огромным event-loopом казался уже не самой перспективной идеей. В то же время Flutter мог позволить нам задействовать более производительную связку рендеринга. Для тех, кто не в теме: первую версию этого SDK с открытым кодом Google представил еще шесть лет назад. Тогда этот набор средств разработки годился только для Android. Сейчас на нем можно писать приложения для веба, iOS, Linux и даже Google Fuchsia. :-) Рабочий язык для разработки приложений на Flutter — Dart, в свое время он был предложен в качестве альтернативы JavaScript. Перед нами стоял вопрос: даст ли переход на Flutter какой-то выигрыш по производительности? Ведь подход там совершенно иной, хоть в конечном счете и имеется та же графическая подсистема Wayland + OpenGL. Ну и как там с поддержкой процессоров с neon-инструкциями? Были и другие вопросы, например, нюансы по переносу UI на dart или то, что поддержка Linux находится в стадии альфы-беты.Сборка Flutter Engine для ТВ-приставок на базе ARM Итак, начнем. Вначале Futter нужно запустить на чужеродной платформе с Wayland + OpenGL ES. В основе рендеринга у Flutter лежит библиотека Skia, которая прекрасно поддерживает OpenGL ES, поэтому в теории все выглядело хорошо. При сборке Flutter под наши целевые устройства (три ТВ-приставки с RDK), к нашему удивлению, проблемы возникли только на одной. Не будем с ней сражаться, т.к. из-за старой архитектуре intel x86 она для нас не является приоритетной. Лучше сосредоточимся на оставшихся двух ARM-платформах. Вот, с какими опциями мы собирали Flutter Engine:
./flutter/tools/gn \
      --embedder-for-target \
      --target-os linux \
      --linux-cpu arm \ 
      --target-sysroot DEVICE_SYSROOT
      --disable-desktop-embeddings \ 
      --arm-float-abi hard 
      --target-toolchain /usr 
      --target-triple arm-linux-gnueabihf 
      --runtime-mode debug
ninja -C out/linux_debug_unopt_arm
Большинство опций понятны: собираем под 32-битный ARM-процессор и Linux, выключая при этом все лишнее через --embedder-for-target --disable-desktop-embeddings. Для сборки в системе должен быть установлен clang версии 9 и выше, т.е. это стандартный сборочный механизм Flutter, инструментарий кросс-компиляции gcc не пойдет. Самое важное — подать корректный target-sysroot устройства с RDK.Честно говоря, мы удивились, что при сборке не возникло вообще никаких нюансов. На выходе получаем заветную библиотеку flutter_engine.so и заголовок с необходимыми функциями для эмбеддера. Теперь можно собрать целевой проект flutter/dart с нашей библиотекой/движком. Это сделать легко:
flutter --local-engine-src-path PATH_TO_BUILDED_ENGINE_src
--local-engine=host_debug_unopt build bundle
Важно! Сборка проекта должна происходить не на устройстве с собранной библиотекой, а на хостовой, т.е. x86_64!
Для этого достаточно еще раз пройти путь сборкой gn и ninja только под x86_64! Именно она указывается в параметре host_debug_unopt. PATH_TO_BUILDED_ENGINE_src — это путь, где находится engine/src/out.За запуск Flutter Engine под системой обычно отвечает embedder, именно он конфигурирует Flutter под целевую систему и дает основные контексты рендеринга библиотеке Skia и Dart-обработчику. Не так давно в состав Flutter добавили linux-embedder, и, в частности, GTK-embedder, так что можно воспользоваться им из коробки. На нашей платформе на момент портирования это был не вариант, нужно было что-то независимое от GTK. Рассмотрим некоторые особенности реализации, которые пришлось учесть с кастомным эмбеддером (все, кто  любит разбирать не нюансы, а исходники целиком, может сразу перейти к проекту нашего форка с доработками на github.com). К тому же по производительности наш вариант немного выигрывал у версии GTK, что было крайне важно для заказчика, и не тянул за собой весь зоопарк GTK-библиотек.Так что же вообще нужно от эмбеддера для запуска flutter-приложения? Достаточно, чтобы он просто вызывал из библотеки flutter_engine.soFlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, display /* userdata */, &engine_);где в качестве параметров идет передача настроек проекта (директория с собранным flutter bundle) FlutterProjectArgs args и аргументов рендеринга FlutterRendererConfig config. В первой структуре как раз задается путь bundle-пакета, собранного flutter-утилитой, а во второй используются контексты OpenGL .// пример использования на github.com/DEgITx/flutter_wayland/blob/master/src/flutter_application.cc Все довольно примитивно, но этого достаточно для запуска приложения. Проблемы и их решение Теперь поговорим о нюансах, с которыми мы столкнулись на этапе портирования. А как же без них? Не только ведь библиотеки собирать :-) 1. Краш эмбеддера и замена очередности вызова функцийПервая проблема, с которой мы столкнулись — краш эмбеддера под платформой. Казалось бы, инициализация egl-контекста в других приложения происходит нормально, FlutterRendererConfig инициализирован корректно, но нет — эмбеддер не заводится. Значит в связке что-то явно не так. Оказалось, eglBindAPI нельзя вызывать перед eglGetDisplay, на котором происходит особая инициализация nexus-драйвера дисплея (у нас платформа базируется на чипе BCM). В обычном Linux это не проблема, но на целевой платформе оказалась иначе. Корректная инициализация эмбеддера выглядит так:
egl_display_ = eglGetDisplay(display_);
if (egl_display_ == EGL_NO_DISPLAY) {
  LogLastEGLError();
  FL_ERROR("Could not access EGL display.");
  return false;
}
if (eglInitialize(egl_display_, nullptr, nullptr) != EGL_TRUE) {
  LogLastEGLError();
  FL_ERROR("Could not initialize EGL display.");
  return false;
}
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) {
  LogLastEGLError();
  FL_ERROR("Could not bind the ES API.");
  return false;
}
// github.com/DEgITx/flutter_wayland/blob/master/src/wayland_display.cc — корректная реализация, т.е. помогла измененная очередность вызова функций.Теперь, когда нюанс запуска улажен, мы рады увидеть заветное демо-окно приложения на экране :-).
2. Оптимизация производительностиНастало время проверить производительность. И, честно говоря, она нас не сильно порадовала в режиме отладки (debug mode). Что-то работало шустро, что-то — наоборот, имело большие просадки по фреймам и тормозило гораздо больше, чем что-то похожее на EFL+Node.js.Мы немного расстроились и начали копать дальше. В SDK Flutter есть специальный режим компиляции машинного кода AOT, это даже не jit, а именно компиляция в нативный код со всеми сопутствующими оптимизациями, именно это подразумевается под по релиз-версией  Flutter. Такой поддержки у нас в эмбеддере пока не было, добавляем.Необходимы определенные инструкции, поданные аргументами к FlutterEngineRun// полная реализация — github.com/DEgITx/flutter_wayland/blob/master/src/elf.cc
vm_snapshot_instructions_ = dlsym(fd, "_kDartVmSnapshotInstructions");
if (vm_snapshot_instructions_ == NULL) {
  error_ = strerror(errno);
  break;
}
vm_isolate_snapshot_instructions_ = dlsym(fd, "_kDartIsolateSnapshotInstructions");
if (vm_isolate_snapshot_instructions_ == NULL) {
  error_ = strerror(errno);
  break;
}
vm_snapshot_data_ = dlsym(fd, "_kDartVmSnapshotData");
if (vm_snapshot_data_ == NULL) {
  error_ = strerror(errno);
  break;
}
vm_isolate_snapshot_data_ = dlsym(fd, "_kDartIsolateSnapshotData");
if (vm_isolate_snapshot_data_ == NULL) {
  error_ = strerror(errno);
  break;
}
if (vm_snapshot_data_ == NULL ||
vm_snapshot_instructions_ == NULL ||
vm_isolate_snapshot_data_ == NULL ||
vm_isolate_snapshot_instructions_ == NULL) {
  return false;
}
*vm_snapshot_data = reinterpret_cast <
  const uint8_t * > (vm_snapshot_data_);
*vm_snapshot_instructions = reinterpret_cast <
  const uint8_t * > (vm_snapshot_instructions_);
*vm_isolate_snapshot_data = reinterpret_cast <
  const uint8_t * > (vm_isolate_snapshot_data_);
*vm_isolate_snapshot_instructions = reinterpret_cast <
  const uint8_t * > (vm_isolate_snapshot_instructions_);
FlutterProjectArgs args;
// передаем все необходимое в args
args.vm_snapshot_data = vm_snapshot_data;
args.vm_snapshot_instructions = vm_snapshot_instructions;
args.isolate_snapshot_data = vm_isolate_snapshot_data;
args.isolate_snapshot_instructions = vm_isolate_snapshot_instructions;
Теперь, когда все есть, нужно собрать приложение особым образом, чтобы получить AOT-скомпилированный модуль под целевую платформу. Это можно сделать, выполнив команду из корня dart-проекта:
$HOST_ENGINE/dart-sdk/bin/dart \
--disable-dart-dev \
$HOST_ENGINE/gen/frontend_server.dart.snapshot \
--sdk-root $DEVICE_ENGINE}/flutter_patched_sdk/ \
--target=flutter \
-Ddart.developer.causal_async_stacks=false \
-Ddart.vm.profile=release \
-Ddart.vm.product=release \
--bytecode-options=source-positions \
--aot \
--tfa \
--packages .packages \
--output-dill build/tmp/app.dill \
--depfile build/kernel_snapshot.d \
package:lib/main.dart
$DEVICE_ENGINE/gen_snapshot                               \
    --deterministic                                             \
    --snapshot_kind=app-aot-elf                                 \
    --elf=build/lib/libapp.so                                   \
    --no-causal-async-stacks                                    \
    --lazy-async-stacks                                         \
    build/tmp/app.dill
Не нужно пугаться огромного количества параметров, большинство из них является стандартными. В частности:
-Ddart.vm.profile=release \
-Ddart.vm.product=release \
указывают на то, что нам не нужен профайлер в комплекте и у нас продуктовая версия.output-dill нужен для построения нативной библитеки libapp.so. Самыми важными для нас являются пути $DEVICE_ENGINE и $HOST_ENGINE — два собранных движка под целевую (ARM) и хост-системы (x86_64) соответственно. Тут важно ничего не перепутать и убедиться, что libapp.so получается именно 32-битной ARM-версией:
$ file libapp.so
libapp.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV),
dynamically linked
Запускаем и-и-и-и... вуаля! — все работает! И работает шустрее значительно! Теперь уже можно говорить о сравнимой производительности и эффективности рендеринга с исходным приложением на базе набора библиотек EFL. Рендеринг работает почти без запинки и почти идеально на простых приложениях. 3. Подключение устройств вводаВ рамках этой статьи мы пропустим то, как подружили Wayland и эмбеддер с пультом, мышкой и другими устройствами ввода. На их реализацию можно посмотреть в исходниках эмбеддера. 4. Интерфейс на ТВ-приставке под Linux и Android и как увеличить производительность в 2—3 раза Коснемся еще нескольких нюансов производительности, с которыми столкнулись в продуктовом UI-приложении. Нас очень обрадовала идентичность работы UI как на целевом устройстве, так и на Linux и Android. Уже сейчас Flutter может вполне может похвастаться очень гибкой портируемостью.Еще отметим интересный опыт оптимизации самого dart-приложения под целевую платформу. Нас разочаровала довольно низкая производительность продуктового приложения (в отличии от демок). Мы взяли в руки профайлер и начали копать идовольно быстро обнаружили активное использование функций __brcm_cpu_dcache_flush и khrn_copy_8888_to_tf32 во время анимаций (на платформе используется чип процессора Broadcom/BCM ). Явно происходило какое-то очень жесткое пиксельное программное трансформирование или копирование во время анимаций. В итоге виновник был найден: в одной из панелей был задействован эффект размытия: 
//...
filter: new ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
//...
Комментирование одного этого эффекта дало увеличение производительности приложения в два—три раза и вывело приложение на стабильные 50-60 fps. Это хороший пример того, как один специфический эффект может уничтожить производительность во всем приложении на Flutter при том, что он был зайствован всего-лишь в одной панели, которая большую часть времени была скрыта.ИтогоВ результате мы получили не просто работающее продуктовое приложение, а работающее приложение с качественным фреймрейтом на Flutter на целевом устройстве. Форк и наша версия эмбеддера под RDK и другие платформы на основе Wayland находится тут: github.com/DEgITx/flutter_waylandНадеемся, опыт нашей команды в разработке и портировании ПО для ТВ-приставок и Smart TV пригодится вам в своих проектах и послужит отправной точкой для портирования Flutter на других устройствах.[!?] Вопросы и комментарии приветствуются. На них будет отвечать автор статьи Алексей Касьянчук, наш инженер-программист
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_razrabotka_pod_android (Разработка под Android), #_dart, #_razrabotka_pod_linux (Разработка под Linux), #_proizvodstvo_i_razrabotka_elektroniki (Производство и разработка электроники), #_flutter, #_android_tv, #_prilozhenija_dlja_smart_tv (приложения для smart tv), #_flutter, #_cpp, #_bash, #_javascript, #_promwad, #_rdk, #_ott, #_linux, #_razrabotka_pod_android (
Разработка под Android
)
, #_dart, #_razrabotka_pod_linux (
Разработка под Linux
)
, #_proizvodstvo_i_razrabotka_elektroniki (
Производство и разработка электроники
)
, #_flutter
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 14:49
Часовой пояс: UTC + 5