Структура синхронизации

Фреймворк синхронизации явно описывает зависимости между различными асинхронными операциями в графической системе Android. Фреймворк предоставляет API, позволяющий компонентам сообщать об освобождении буферов. Фреймворк также позволяет передавать примитивы синхронизации между драйверами из ядра в пользовательское пространство и между самими процессами пользовательского пространства.

Например, приложение может поставить в очередь задачи для выполнения в графическом процессоре. Графический процессор начинает отрисовку изображения. Хотя изображение ещё не отрисовано в памяти, указатель на буфер передаётся в оконный компоновщик вместе с границей, указывающей время завершения работы графического процессора. Оконный компоновщик начинает обработку заранее и передаёт работу контроллеру дисплея. Аналогичным образом, работа центрального процессора выполняется заранее. После завершения работы графического процессора контроллер дисплея немедленно отображает изображение.

Фреймворк синхронизации также позволяет разработчикам использовать ресурсы синхронизации в своих аппаратных компонентах. Наконец, фреймворк обеспечивает прозрачность графического конвейера для облегчения отладки.

Явная синхронизация

Явная синхронизация позволяет производителям и потребителям графических буферов сообщать о завершении использования буфера. Явная синхронизация реализована в пространстве ядра.

Преимущества явной синхронизации включают в себя:

  • Меньше различий в поведении между устройствами
  • Лучшая поддержка отладки
  • Улучшенные показатели тестирования

Фреймворк синхронизации имеет три типа объектов:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline — это монотонно увеличивающаяся временная шкала, которую поставщики должны реализовать для каждого экземпляра драйвера, например контекста GL, контроллера дисплея или 2D-блитера. sync_timeline подсчитывает задания, отправленные ядру для конкретного элемента оборудования. sync_timeline предоставляет гарантии относительно порядка операций и позволяет реализовать специфичные для оборудования реализации.

При реализации sync_timeline следуйте этим рекомендациям:

  • Дайте полезные имена всем драйверам, временным шкалам и границам для упрощения отладки.
  • Реализуйте операторы timeline_value_str и pt_value_str на временных шкалах, чтобы сделать вывод отладки более читабельным.
  • Реализуйте заполнение driver_data , чтобы предоставить библиотекам пользовательского пространства, таким как библиотека GL, доступ к приватным данным временной шкалы, если это необходимо. data_driver позволяет поставщикам передавать информацию о неизменяемых sync_fence и sync_pts для создания командных строк на их основе.
  • Не позволяйте пользовательскому пространству явно создавать или подавать сигналы о барьере. Явное создание сигналов/барьеров приводит к атаке типа «отказ в обслуживании», которая останавливает работу конвейера.
  • Не обращайтесь к элементам sync_timeline , sync_pt или sync_fence явно. API предоставляет все необходимые функции.

sync_pt

sync_pt — это отдельное значение или точка на шкале sync_timeline . Точка имеет три состояния: активное, сигнальное и ошибка. Точки изначально находятся в активном состоянии и переходят в сигнальное или ошибочное. Например, когда потребителю изображения больше не нужен буфер, sync_pt получает сигнал, чтобы производитель изображения знал, что можно снова записывать данные в буфер.

sync_fence

sync_fence — это набор значений sync_pt , которые часто имеют разных родительских элементов sync_timeline (например, для контроллера дисплея и графического процессора). sync_fence , sync_pt и sync_timeline — основные примитивы, которые драйверы и пользовательское пространство используют для передачи своих зависимостей. Когда барьер получает сигнал, все команды, выполненные до него, гарантированно выполняются, поскольку драйвер ядра или аппаратный блок выполняют команды по порядку.

Фреймворк синхронизации позволяет нескольким потребителям или производителям сообщать о завершении использования буфера, передавая информацию о зависимостях одним параметром функции. Ограничения поддерживаются файловым дескриптором и передаются из пространства ядра в пространство пользователя. Например, ограничение может содержать два значения sync_pt , которые указывают, когда два отдельных потребителя изображений завершили чтение буфера. Когда ограничение получает сигнал, производители изображений знают, что оба потребителя завершили использование.

Заборы, как и значения sync_pt , изначально активны и меняют состояние в зависимости от состояния своих точек. Если все значения sync_pt переходят в состояние «сигнал», весь sync_fence также переходит в состояние «сигнал». Если один sync_pt переходит в состояние ошибки, весь sync_fence переходит в состояние ошибки.

Членство в sync_fence не изменяется после создания ограждения. Чтобы получить более одной точки в ограждении, выполняется слияние, при котором точки из двух разных ограждений добавляются в третье ограждение. Если одна из этих точек была в состоянии сигнала в исходном ограждении, а другая — нет, третье ограждение также не будет в состоянии сигнала.

Для реализации явной синхронизации предоставьте следующее:

  • Подсистема пространства ядра, реализующая фреймворк синхронизации для конкретного драйвера оборудования. Драйверы, которым требуется поддержка Fagon-Aware, — это, как правило, любые драйверы, которые обращаются к Hardware Composer или взаимодействуют с ним. Ключевые файлы включают:
    • Основная реализация:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • Документация в kernel/common/Documentation/sync.txt
    • Библиотека для взаимодействия с пространством ядра в platform/system/core/libsync
  • Поставщик должен предоставить соответствующие границы синхронизации в качестве параметров для функций validateDisplay() и presentDisplay() в HAL.
  • Два расширения GL, связанных с Fence ( EGL_ANDROID_native_fence_sync и EGL_ANDROID_wait_sync ), и поддержка Fence в графическом драйвере.

Пример использования: Реализация драйвера дисплея

Чтобы использовать API, поддерживающий функцию синхронизации, разработайте драйвер дисплея с функцией буферизации дисплея. До появления фреймворка синхронизации эта функция получала объекты dma-buf , выводила эти буферы на дисплей и блокировалась, пока буфер был видим. Например:

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

В фреймворке синхронизации функция display_buffer становится сложнее. При отображении буфера на экране он связывается с ограничением, которое указывает, когда буфер будет готов. Вы можете поставить буфер в очередь и начать работу после того, как ограничение будет снято.

Постановка в очередь и запуск работы после очистки границы ничего не блокирует. Вы немедленно возвращаете свою собственную границу, что гарантирует момент, когда буфер исчезнет с экрана. При постановке буферов в очередь ядро ​​перечисляет зависимости с помощью фреймворка синхронизации:

/*
 * displays buffer when fence is signaled.  returns immediately with a fence
 * that signals when buffer is no longer displayed.
 */
struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence
*fence);

Синхронизация интеграции

В этом разделе объясняется, как интегрировать фреймворк синхронизации пространства ядра с пользовательскими компонентами фреймворка Android и драйверами, которые должны взаимодействовать друг с другом. Объекты пространства ядра представлены в пользовательском пространстве как файловые дескрипторы.

Интеграционные соглашения

Следуйте соглашениям интерфейса Android HAL:

  • Если API предоставляет файловый дескриптор, ссылающийся на sync_pt , драйвер поставщика или HAL, использующий API, должен закрыть файловый дескриптор.
  • Если драйвер поставщика или HAL передают файловый дескриптор, содержащий sync_pt , в функцию API, драйвер поставщика или HAL не должны закрывать файловый дескриптор.
  • Чтобы продолжить использование дескриптора файла ограждения, драйвер поставщика или HAL должны продублировать дескриптор.

Объект ограждения переименовывается каждый раз при прохождении через BufferQueue. Поддержка ограждений на уровне ядра позволяет им использовать строковые имена, поэтому фреймворк синхронизации использует имя окна и индекс буфера, помещаемого в очередь, для присвоения имени ограждению, например, SurfaceView:0 . Это полезно при отладке для определения источника взаимоблокировки, поскольку имена появляются в выводе /d/sync и отчётах об ошибках.

Интеграция ANativeWindow

ANativeWindow распознает ограничения. dequeueBuffer , queueBuffer и cancelBuffer есть параметры ограничения.

Интеграция OpenGL ES

Интеграция синхронизации OpenGL ES основана на двух расширениях EGL:

  • EGL_ANDROID_native_fence_sync предоставляет способ обернуть или создать собственные дескрипторы файлов ограждения Android в объектах EGLSyncKHR .
  • EGL_ANDROID_wait_sync допускает задержку на стороне графического процессора, а не на стороне центрального процессора, заставляя графический процессор ждать EGLSyncKHR . Расширение EGL_ANDROID_wait_sync аналогично расширению EGL_KHR_wait_sync .

Чтобы использовать эти расширения независимо, реализуйте расширение EGL_ANDROID_native_fence_sync вместе с соответствующей поддержкой ядра. Затем включите расширение EGL_ANDROID_wait_sync в вашем драйвере. Расширение EGL_ANDROID_native_fence_sync представляет собой отдельный нативный тип объекта EGLSyncKHR . В результате расширения, применяемые к существующим типам объектов EGLSyncKHR , не обязательно применяются к объектам EGL_ANDROID_native_fence , что позволяет избежать нежелательных взаимодействий.

Расширение EGL_ANDROID_native_fence_sync использует соответствующий атрибут дескриптора файла-забора, который можно задать только при создании и нельзя напрямую запросить из существующего объекта синхронизации. Этот атрибут может быть установлен в одном из двух режимов:

  • Допустимый дескриптор файла ограждения оборачивает существующий собственный дескриптор файла ограждения Android в объект EGLSyncKHR .
  • -1 создает собственный дескриптор файла ограждения Android из объекта EGLSyncKHR .

Используйте вызов функции DupNativeFenceFD() для извлечения объекта EGLSyncKHR из нативного файлового дескриптора ограждения Android. Это даёт тот же результат, что и запрос атрибута set, но соответствует соглашению, согласно которому получатель закрывает ограждение (отсюда и дублирование операции). Наконец, уничтожение объекта EGLSyncKHR закрывает внутренний атрибут ограждения.

Интеграция с аппаратным Composer

Hardware Composer обрабатывает три типа границ синхронизации:

  • Заборы Acquire передаются вместе с входными буферами в вызовы setLayerBuffer и setClientTarget . Они представляют собой ожидающую запись в буфер и должны подать сигнал до того, как SurfaceFlinger или HWC попытаются прочитать данные из соответствующего буфера для выполнения композиции.
  • Границы освобождения извлекаются после вызова presentDisplay с помощью вызова getReleaseFences . Они представляют собой ожидающее чтение из предыдущего буфера на том же слое. Граница освобождения сигнализирует, когда HWC больше не использует предыдущий буфер, поскольку текущий буфер заменил предыдущий буфер на дисплее. Границы освобождения передаются обратно в приложение вместе с предыдущими буферами, которые будут заменены в ходе текущей композиции. Приложение должно дождаться сигнала границы освобождения, прежде чем записывать новое содержимое в возвращённый ему буфер.
  • Текущие границы возвращаются, по одной на кадр, как часть вызова presentDisplay . Текущие границы представляют собой момент завершения формирования текущего кадра или, наоборот, момент, когда результат формирования предыдущего кадра больше не нужен. Для физических дисплеев presentDisplay возвращает текущие границы, когда текущий кадр появляется на экране. После возвращения текущих границ можно снова безопасно записывать данные в целевой буфер SurfaceFlinger, если это применимо. Для виртуальных дисплеев текущие границы возвращаются, когда становится возможным безопасно читать данные из выходного буфера.