На этой странице подробно описан полный графический конвейер высокодоступного рендерера (HAR), отслеживающий поток данных от проектного документа Figma до конечных пикселей, отображаемых на экране.
Обзор
Конвейер преобразует высокоуровневые определения пользовательского интерфейса в низкоуровневые графические команды и эффективно отображает их на аппаратных дисплеях. Конвейер разработан для критически важных с точки зрения безопасности автомобильных приложений, с акцентом на детерминированное рендеринг, эффективное управление состоянием и надежное взаимодействие с графическими подсистемами платформы, такими как Direct Rendering Manager (DRM) и Generic Buffer Management (GBM).
Процесс можно разделить на четыре основных этапа:
- Предварительная отрисовка: обработка графа сцены, применение настроек и разрешение компоновки.
- Генерация команд: Преобразование графа разрешенной сцены в список отображаемых данных, не зависящий от используемой серверной части.
- Рендеринг: Выполнение команд отрисовки с использованием графического движка Impeller.
- Презентация: Управление буферами кадров и синхронизация с аппаратным обеспечением дисплея.

Рисунок 1. Графическая схема HAR.
Этап 1: Предварительная отрисовка
На этом этапе статический дизайн Figma и динамическое состояние приложения преобразуются в полностью сформированное дерево пользовательского интерфейса в памяти, готовое к рендерингу. Этот этап выполняется в отдельном потоке редуктора, независимом от основного цикла отображения.
1.1 Основа DesignCompose
Конвейер обработки изображений HAR построен на основе экосистемы DesignCompose.
- Источник: Пользовательский интерфейс разработан в Figma и экспортирован с помощью плагина DesignCompose.
- Определение: Результатом является экземпляр класса
DesignComposeDefinition, сериализованное представление проекта (узлы, стили, варианты). - Привязка данных: В модели пользовательского интерфейса приложения используются процедурные макросы (например,
#[Design(node = "#speed")]) для явной привязки полей структуры Rust к конкретным именованным узлам в документе Figma. Это позволяет состоянию приложения автоматически управлять свойствами визуальных элементов.
Ключевыми компонентами этого фундамента являются:
- Редуктор: выступает в качестве центрального цикла обработки событий, обрабатывая действия и обновляя текущее состояние. Фреймворк предоставляет
DefaultReducer, но при необходимости может быть предоставлена собственная реализация редуктора. - Presenter: Связывает текущее состояние с моделью пользовательского интерфейса. Трейт
Presenterопределяется библиотекой фреймворкаharry, а эталонная реализация (UIModelPresenter) предоставляется в библиотекеharry-app-core. - Модель пользовательского интерфейса: генерирует настройки на основе текущего состояния. Код модели пользовательского интерфейса генерируется с помощью макроса
DesignDocumentпредоставляемого крейтомderive_customizations. СтруктураUIModelв крейтеharry-app-coreпредоставляет пример этого. - Squoosh: Предоставляет структуру данных
SquooshViewи репозиторий вариантов, используемых для отрисовки пользовательского интерфейса в соответствии с дизайном. Сериализованный документ дизайна загружается библиотекойdc_bundleиз DesignCompose и преобразуется в дерево структурSquooshViewдля повышения эффективности во время выполнения.
1.2 Редукторная петля
Конвейер обработки данных управляется действиями. В фреймворке указан перечисляемый тип Actions , который определяет внутренние действия, используемые самим фреймворком, а также вариант CustomAction , позволяющий пользователям определять дополнительные действия, специфичные для приложения (например, UpdateVehicleSpeed или ButtonPress ).
Фреймворк также предоставляет трейт StateAction , который упрощает реализацию действий, влияющих на состояние приложения и, при необходимости, генерирующих побочные эффекты, которые затем передаются обратно в приложение из редуктора для обработки. Подробный пример этого можно найти в перечислении CustomActions в крейте harry-app-core .
Это базовая схема цикла редуктора:
- Обработка действий:
Reducerполучает действие и обновляет текущее состояние. Это исходные данные, такие как текущая скорость или какие индикаторы (предупреждающие лампы) активны. Это также может вызывать побочные эффекты (например, звуковой сигнал, воспроизводимый при мигании индикатора ремня безопасности). - Презентация:
Presenterотображает новое состояние вUIModel.UIModel— это модель представления, содержащая данные, специально отформатированные для пользовательского интерфейса (например, форматирование скорости "120" в строку "65 миль/час"). - Генерация настроек: вызывается метод
applyмодели пользовательского интерфейса для генерации набора экземпляровRenderCustomization. Это явные инструкции по изменению дизайна Figma (например, "Установить текст узла #speed на '65 миль/час'"). -
UpdatePolicyдля оптимизации: После каждого прохода предварительной отрисовки возвращается значениеUpdatePolicy, указывающее, когда потребуется следующее обновление отрисовки. Если нет ожидающих изменений состояния и не запущены анимации,UpdatePolicyсигнализирует о том, что дальнейшие обновления немедленно не требуются. В таких случаях Reducer прекращает генерацию новых списков отображения, предотвращая ненужные циклы отрисовки и экономя ресурсы до тех пор, пока новое действие или событие не вызовет изменение.
1.3 Инициализация процесса загрузки и репозитория представлений
Процесс начинается с экземпляра DesignComposeDefinition . Это проектный документ Figma, сериализованный DesignCompose в структуру протокола буферизации.
Первоначальная загрузка: При запуске основной проект (указанный его корневым узлом) преобразуется из
DesignComposeDefinitionв начальное деревоSquooshView. Это одноразовый процесс.Репозиторий:
SquooshVariantRepositoryуправляет многократно используемыми вариантами компонентов и первоначально загружаемыми представлениями.Ленивая загрузка: Чтобы минимизировать время запуска и использование памяти, дополнительные представления (не входящие в исходное дерево корневых узлов) загружаются из документа лениво только тогда, когда на них явно ссылается и они необходимы для логики рендеринга (например, при настройке списка).
1.4. Проход для настройки
Для применения динамического состояния приложения выполняется обход дерева SquooshView :
Замена вариантов: Экземпляры компонентов заменяются на определенные варианты (например, значок, представляющий текущий режим движения, меняется со спортивного на экономичный) на основе логики, определяемой временем выполнения.
Расширение списка: В Figma один элемент шаблона заменяется динамическим списком дочерних элементов. Для этих дочерних элементов генерируются новые уникальные идентификаторы для обеспечения стабильной идентификации анимаций.
Переопределение текста и стиля: текстовое содержимое (например, значение скорости) и стили (например, прозрачность, цвет) обновляются на основе текущего состояния.
1.5 Переменное разрешение
Токены дизайна и переменные, определенные в Figma или локально в приложении, обрабатываются.
- Привязка: Свойства
SquooshView, ссылающиеся на переменные (например, цвета или размеры), заменяются их конкретными значениями для текущего кадра.
1.6 Расчет компоновки
Динамическая компоновка:
DynamicLayoutвычисляет конечное положение и размер (границы) каждого узла в деревеSquooshView.Макет текста:
TextHelperиспользует реализацию трейтаLayoutHelperдля вычисления метрик текста, переноса и формирования. Это помогает убедиться, что текст правильно располагается в пределах заданных ограничений перед отрисовкой.
1.7 Циферблаты и датчики
Это специализированный этап для автомобильных пользовательских интерфейсов.
-
MeterData: Если узел содержит данные счетчика (определенные в Figma), его геометрия динамически изменяется в зависимости отmeter_value(например, скорости транспортного средства).- Дуги: Угол развертки регулируется.
- Вращения: Преобразование вращения рассчитывается на основе начального и конечного углов.
- Индикаторы выполнения: ширина или высота прямоугольника масштабируются.
- Векторы прогресса: Длина траектории вектора корректируется.
1.8 Анимация
Сравнение: Текущий
SquooshViewсравнивается сprevious_squoosh_viewизPreRenderCache.Интерполяция: Если свойства изменились,
Squooshсоздает интерполяторы для плавного изменения значений (например, прозрачности или преобразования) во времени.
Этап 2: Генерация команд
После того, как дерево SquooshView полностью сформировано и анимировано, оно преобразуется в линейную последовательность команд рисования.
Ключевым компонентом этого этапа является библиотека DisplayList :
generate_dl: Эта функция рекурсивно обходит деревоSquooshView.Перевод:
- Фигуры и контуры: Преобразованы в
DisplayListEntryс соответствующим вариантомDisplayListAppearance(например,RectилиPath). - Текст: Преобразовано с помощью
TextHelperв текстовые графические элементы. - Преобразования и отсечения: преобразуются в пары
PushTransform3DиPopTransform3DилиPushClipRegionиPopClipRegionдля управления стеком состояний отрисовки. - Маскирование: преобразовано в пары
PushMaskLayerиPopMaskLayerдля корректного создания и смешивания слоев.
- Фигуры и контуры: Преобразованы в
В итоге получается экземпляр класса Vec<DisplayListEntry> , описывающий, что нужно нарисовать, независимо от способа рисования.
2.1 Передача управления петлеобразователю
После генерации DisplayList , Reducer оборачивает его в экземпляр ViewDescriptor и отправляет по каналу Rust MPSC ( LooperMessage ) потоку Looper. Looper отвечает за этапы рендеринга и отображения, что предотвращает блокировку графического конвейера потоком Reducer.
Этап 3: Рендеринг
Независимый от платформы DisplayList передается в бэкэнд рендеринга, где абстрактные команды преобразуются в инструкции для графического процессора.
HAR использует Impeller, движок рендеринга, первоначально разработанный для Flutter. Impeller предназначен для решения проблемы сбоев частоты кадров, возникающих из-за компиляции шейдеров, путем предварительной компиляции небольшого, эффективного набора шейдеров во время сборки. Такой подход в сочетании с эффективной пакетной обработкой и высокооптимизированным бэкендом обеспечивает:
- Детерминированная производительность: практически исключает ошибки компиляции шейдеров во время выполнения.
- Быстрый запуск: снижает накладные расходы на инициализацию.
- Небольшой размер: обеспечивает компактный двоичный формат.
Для более подробного ознакомления с архитектурой Impeller посмотрите видео [Представляем Impeller — новый движок рендеринга Flutter][impeller-video]. Хотя в видео обсуждается Flutter, эти основные преимущества напрямую расширяют возможности автомобильного стека HAR.
Ключевыми компонентами этапа рендеринга являются:
ImpellerRenderer: Преобразует список отображаемых данных из фазы предварительной отрисовки в команды отрисовки Impeller.API Impeller для Rust: Обёртка для библиотеки Impeller, предназначенная для использования в Rust (крейты
impellerиimpeller-rs-bindgen).TypographyContext: Управляет регистрацией шрифта и изменением формы текста.
3.1 Инициализация и управление поверхностью
Создание контекста: Рендерер инициализирует экземпляр
impeller::Contextс бэкендом OpenGL ES, передавая функцию обратного вызова для разрешения указателей на функции OpenGL ES из контекста GL платформы.Обернутая поверхность FBO: Вместо создания собственного окна Impeller выполняет рендеринг в существующий объект кадрового буфера OpenGL (FBO), предоставленный Phase 4. Это делается путем вызова
Surface::create_wrapped_fbo.
3.2 Управление ресурсами
Изображения: Поддерживаются стандартные форматы и текстуры, сжатые по KTX2. Они загружаются в текстуры графического процессора и управляются внутренней структурой
Resources.Шрифты: для отображения текста загружаются и регистрируются шрифты TrueType и OpenType в объекте
TypographyContext.Внешние изображения: Специализированная обработка внешних текстур (например, видеопотоков с камер и внешних 3D-рендеров) включает привязку экземпляров
EGLImageили внешних текстур OpenGL к объектам ImpellerTextureдля рендеринга без копирования.
3.3 Проход рендеринга
Цикл render создает экземпляр Impeller DisplayList (не путать с Vec<DisplayListEntry> , сгенерированным на этапе предварительного рендеринга) с помощью DisplayListBuilder :
Очищает буфер и применяет глобальные преобразования для масштабирования DPI и поворота дисплея.
Выполняет итерацию по элементам входного
DisplayListEntry:- Состояние:
save()иrestore()используются для добавления и удаления преобразований и областей обрезки. - Примитивы:
RectиRoundedRectотрисовываются с использованием стандартных операций рисования. - Пути: Создаются и отображаются сложные векторные пути (включая динамические экземпляры
Arc). - Текст:
TextиStyledTextотображаются с использованиемTypographyContext. - Изображения: Стандартные и внешние изображения отрисовываются с помощью
draw_texture_rect.
- Состояние:
Отправляет созданный список отображаемых объектов Impeller на поверхность с помощью
surface.draw_display_list(), генерируя соответствующие команды GL.Вызывает
swap_buffers()для базового контекста, чтобы запустить фазу 4.
Этап 4: Презентация
На заключительном этапе осуществляется взаимодействие с аппаратным обеспечением дисплея для отображения отрендеренного кадра. HAR использует надежный прямой путь рендеринга в программно-определяемой системе управления транспортными средствами (SDV) на базе Android Automotive OS (AAOS).
Ключевым компонентом этого этапа является HarDirectRenderingContext (из крейта har-gl-context ).
4.1 Архитектура
В слое представления используется подход с двойной буферизацией и целевым объектом отрисовки за пределами экрана:
Буфер отрисовки: Внеэкранный FBO, через который Impeller выполняет рендеринг сцены.
Буфер разрешения (опционально): Дополнительный буфер, необязательный для поддержки многосэмплового сглаживания (MSAA).
- Эта функция может быть включена при необходимости в зависимости от базовой реализации или конфигурации OpenGL ES. В таких случаях она служит промежуточным целевым объектом для разрешения буфера многосэмплированной отрисовки перед копированием (передачей битового блока) в буфер рендеринга.
Буфер рендеринга: универсальный буфер, поддерживаемый объектом GBM, который соответствует буферу резервного копирования в типичной цепочке обмена графическими файлами.
Передний буфер: буфер GBM, содержимое которого выводится на дисплей.
4.2 Цепочка обмена
При вызове функции swap_buffers , HAR выполняет следующие действия:
Выполняет копирование содержимого буфера отрисовки в буфер рендеринга (с промежуточным копированием в буфер разрешения, если это необходимо для реализации).
Вызывает функцию
glFlush()для контекста GL и создает экземплярEGL_SYNC_NATIVE_FENCE_ANDROIDдля отслеживания завершения работы графического процессора.Создает атомарный запрос DRM для переключения буфера рендеринга на экран. Этот запрос содержит файловый дескриптор (называемый входным дескриптором) буфера рендеринга, предотвращающий его отображение контроллером дисплея до завершения отрисовки графическим процессором.
Одновременно запрашивает у DRM новый буфер (называемый исходящим буфером), чтобы сигнализировать о том, что предыдущий буфер (передний буфер для предыдущего кадра) больше не отображается на экране.
Подтверждает атомарный запрос, используя неблокирующий флаг, чтобы основной поток мог продолжить работу, пока графические подсистемы остаются синхронизированными.
Сохраняет новый выходной буфер в контексте, чтобы HAR мог дождаться его сигнала в начале процесса
swap_buffersв следующем кадре. Это предотвращает отрисовку графическим процессором буфера, который все еще отображается.
4.3 Настройка прямого режима
HAR взаимодействует напрямую с ядром, используя подсистемы DRM и настройки режима ядра (KMS) для конфигурации разрешения дисплея AAOS SDV, минуя взаимодействие с оконными менеджерами, такими как SurfaceFlinger (в определенных конфигурациях), что позволяет осуществлять эксклюзивное и высокоприоритетное управление аппаратным обеспечением дисплея.
4.4 Внешнее отображение
HAR поддерживает делегирование отрисовки определенных элементов пользовательского интерфейса (идентифицируемых тегами в Figma) внешним процессам или потокам. Это полезно для интеграции сложных 3D-сцен (например, визуализации автомобиля из таких движков, как Kanzi или Unity) или другого контента, требующего выделенного контекста OpenGL.
4.4.1 Ключевые компоненты
-
HarExternalRenderContext: Специальный контекст EGL для внешнего сервиса, предназначенный для работы вне экрана. -
SurfacePool: управляет набором буферовLocalSurface(TextureплюсEGLImage) для двойной или тройной буферизации. -
SharedSurfaceExternalImage: потокобезопасная обертка для передачи дескрипторовEGLImageмежду внешним сервисом и основным рендерером.
4.4.2 Рабочий процесс
Рабочий процесс осуществляется в следующей последовательности:
Внешний сервис запускается и регистрируется в основном цикле обработки изображений, определяя, какие теги Figma (например,
#cluster/3d-car) он отображает.Сервис ожидает сигналов
RenderStartот устройства зацикливания, чтобы синхронизировать рендеринг с сигналом VSYNC дисплея.Вне экрана сервис отображает свой контент в буфер кадров, предоставляемый
SurfacePool.Сервис вызывает
swap_buffersдля своего контекста, которая выполняет ротацию пула и делает завершенный кадр доступным в качестве экземпляраSharedSurface.SharedSurfaceоборачивается вExternalImageи передается по каналу Rust MPSC на устройство зацикливания.Основной рендерер Impeller (фаза 3) получает внешнее изображение. Вместо копирования пиксельных данных он напрямую привязывает базовое
EGLImageк текстуре и отрисовывает его как часть основной сцены, обеспечивая композицию без копирования.
4.5 Платформы разработки и тестирования (har-platform-linux)
В целях разработки и тестирования приложения HAR могут быть ориентированы на стандартные среды рабочего стола Linux и безмониторные системы. Эти платформы реализованы в библиотеке crates/reference/platforms/har-platform-linux .
В отличие от производственной платформы AAOS SDV, эти платформы не используют подсистему direct-rendering har-gl-context для вывода на экран. Вместо этого они полагаются на стандартные библиотеки Rust OpenGL:
Оконный режим: использует
winitдля управления окнами и циклов событий, аglutinдля создания контекстов OpenGL ES и интеграции с оконной системой.Безголовый режим: Использует библиотеку
har-gl-contextдля создания контекста внеэкранного буфера с использованием стандартного отображения EGL. Это позволяет выполнять рендеринг во внеэкранный буфер без необходимости видимого окна или прямого доступа к аппаратному обеспечению дисплея, что в основном используется для автоматизированного тестирования или обработки данных на бэкэнде.