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