Платформа синхронизации

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

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

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

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

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

К преимуществам явной синхронизации относятся:

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

Платформа синхронизации имеет три типа объектов:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

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

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

  • Укажите полезные имена для всех драйверов, временных шкал и барьеров, чтобы упростить отладку.
  • Реализуйте операторы timeline_value_str и pt_value_str на временных шкалах, чтобы сделать вывод отладки более читабельным.
  • Реализуйте fill 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 остается неизменным после создания забора. Чтобы получить более одной точки в заборе, выполняется слияние, при котором точки из двух разных заборов добавляются к третьему забору. Если одна из этих точек была сигнализирована в исходном ограждении, а другая нет, третье ограждение также не будет находиться в сигнальном состоянии.

Чтобы реализовать явную синхронизацию, предоставьте следующее:

  • Подсистема пространства ядра, которая реализует структуру синхронизации для определенного драйвера оборудования. Драйверы, которые должны быть осведомлены об ограждении, — это, как правило, все, что обращается к 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, связанные с ограждением ( EGL_ANDROID_native_fence_sync и EGL_ANDROID_wait_sync ) и поддержка ограждения в графическом драйвере.

Практический пример: реализация драйвера дисплея

Чтобы использовать 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 позволяет останавливаться на стороне GPU, а не на стороне CPU, заставляя GPU ждать EGLSyncKHR . Расширение EGL_ANDROID_wait_sync с расширением EGL_KHR_wait_sync .

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

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

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

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

Интеграция аппаратного компоновщика

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

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