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

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

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

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

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

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

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

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

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

  • sync_timeline
  • sync_pt
  • sync_fence

синхронизация_временной_шкалы

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

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

  • Подсистема пространства ядра, реализующая фреймворк синхронизации для конкретного драйвера оборудования. Драйверы, которым необходимо знать о границах, — это, как правило, все, что обращается к 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 Fence в объектах 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 состоит из отдельного собственного типа объекта ограждения EGLSyncKHR . В результате расширения, которые применяются к существующим типам объектов EGLSyncKHR , не обязательно применяются к объектам EGL_ANDROID_native_fence , что позволяет избежать нежелательных взаимодействий.

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

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

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

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

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

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