Фреймворк синхронизации

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

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

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

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

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

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

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

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

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

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

При использовании разметки sync_timeline следуйте перечисленным ниже правилам.

  • Присвойте всем драйверам, временным шкалам и ограждениям понятные названия, чтобы упростить отладку.
  • Используйте операторы timeline_value_str и pt_value_str на временных шкалах, чтобы сделать выходные данные отладки более понятными.
  • Реализуйте заполнение driver_data, чтобы при необходимости предоставить библиотекам пользовательского пространства, например библиотеке GL, доступ к закрытым данным хронологии. data_driver позволяет поставщикам передавать информацию о неизменяемых sync_fence и sync_pts, чтобы на ее основе создавать командные строки.
  • Не разрешайте пространству пользователя явно создавать или сигнализировать о барьере. Явное создание сигналов/ограничений приводит к DoS-атаке, которая останавливает работу конвейера.
  • Не обращайтесь к элементам 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 изменить нельзя. Чтобы получить более одной точки в заборе, проводится слияние, при котором точки из двух разных заборов добавляются к третьему забору. Если одна из этих точек была отмечена в исходном ограждении, а другая – нет, то третье ограждение также не будет отмечено.

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

  • Подсистема в пространстве ядра, которая реализует фреймворк синхронизации для определенного драйвера оборудования. Драйверы, которым необходимо учитывать ограждения, обычно представляют собой все, что получает доступ к композитору оборудования (HWC) или взаимодействует с ним. Ключевые файлы:
    • Основная реализация:
      • 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);

Интеграция с Sync

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

Правила интеграции

Соблюдайте следующие правила интерфейса HAL Android:

  • Если 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 закрывается внутренний атрибут ограждения.

Интеграция с аппаратным композитором

HWC обрабатывает три типа барьеров синхронизации:

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