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