Структура синхронизации явно описывает зависимости между различными асинхронными операциями в графической системе 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в строках времени, чтобы сделать отладочный вывод более читабельным. - Реализуйте функцию `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 — это основные примитивы, которые драйверы и пользовательское пространство используют для обмена информацией о своих зависимостях. Когда срабатывает сигнал об ограничении, все команды, выданные до этого момента, считаются выполненными, поскольку драйвер ядра или аппаратный блок выполняют команды по порядку.
Структура синхронизации позволяет нескольким потребителям или производителям сигнализировать о завершении использования буфера, передавая информацию о зависимостях с помощью одного параметра функции. Сигналы прерывания (fences) поддерживаются дескриптором файла и передаются из пространства ядра в пространство пользователя. Например, сигнал прерывания может содержать два значения 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);
Интеграция синхронизации
В этом разделе объясняется, как интегрировать структуру синхронизации в пространстве ядра с пользовательскими компонентами фреймворка Android и драйверами, которые должны взаимодействовать друг с другом. Объекты в пространстве ядра представлены в пользовательском пространстве в виде файловых дескрипторов.
Соглашения об интеграции
Следуйте соглашениям об использовании интерфейса Android HAL:
- Если API предоставляет дескриптор файла, который ссылается на
sync_pt, драйвер поставщика или HAL, использующий этот API, должны закрыть дескриптор файла. - Если драйвер поставщика или HAL передает в функцию API дескриптор файла, содержащий параметр
sync_pt, драйвер поставщика или 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 закрывает внутренний атрибут ограждения.
Интеграция с Hardware Composer
HWC обрабатывает три типа синхронизационных барьеров:
- В вызовы
setLayerBufferиsetClientTargetвместе с входными буферами передаются сигналы захвата (Acquire fences) . Они представляют собой ожидающую запись в буфер и должны сигнализировать о начале процесса, прежде чем SurfaceFlinger или HWC попытаются прочитать данные из соответствующего буфера для выполнения композиции. - Сигналы освобождения буфера получаются после вызова
presentDisplayс помощью вызоваgetReleaseFences. Они представляют собой ожидающее чтение из предыдущего буфера на том же слое. Сигнал освобождения буфера означает, что HWC больше не использует предыдущий буфер, поскольку текущий буфер заменил предыдущий буфер на дисплее. Сигналы освобождения буфера передаются обратно в приложение вместе с предыдущими буферами, которые будут заменены во время текущей композиции. Приложение должно дождаться сигнала освобождения буфера, прежде чем записывать новое содержимое в возвращенный буфер. - В вызове функции
presentDisplayвозвращаются индикаторы состояния (present fences) , по одному на кадр. Индикаторы состояния указывают на завершение компоновки текущего кадра или, наоборот, на то, что результат компоновки предыдущего кадра больше не требуется. Для физических дисплеевpresentDisplayвозвращает индикаторы состояния, когда текущий кадр появляется на экране. После возврата индикаторов состояния можно безопасно снова записывать данные в целевой буфер SurfaceFlinger, если это применимо. Для виртуальных дисплеев индикаторы состояния возвращаются, когда можно безопасно читать данные из выходного буфера.