El framework de sincronización describe de forma explícita las dependencias entre diferentes operaciones asíncronas en el sistema de gráficos de Android. El framework proporciona una API que permite que los componentes indiquen cuándo se liberan los búferes. El framework también permite que se pasen primitivas de sincronización entre los controladores del kernel al espacio de usuario y entre los procesos del espacio de usuario.
Por ejemplo, una aplicación puede poner en cola el trabajo que se realizará en la GPU. La GPU comienza a dibujar esa imagen. Aunque la imagen aún no se haya dibujado en la memoria, el puntero del búfer se pasa al compositor de ventanas junto con una barrera que indica cuándo finalizará el trabajo de la GPU. El compositor de ventanas comienza el procesamiento con anticipación y pasa el trabajo al controlador de pantalla. De manera similar, el trabajo de la CPU se realiza con anticipación. Una vez que finaliza la GPU, el controlador de pantalla muestra la imagen de inmediato.
El marco de trabajo de sincronización también permite que los implementadores aprovechen los recursos de sincronización en sus propios componentes de hardware. Por último, el framework proporciona visibilidad en la canalización de gráficos para ayudar con la depuración.
Sincronización explícita
La sincronización explícita permite que los productores y consumidores de búferes de gráficos indiquen cuándo terminaron de usar un búfer. La sincronización explícita se implementa en el espacio del kernel.
Estos son algunos de los beneficios de la sincronización explícita:
- Menos variación de comportamiento entre dispositivos
- Mejor asistencia para la depuración
- Métricas de prueba mejoradas
El framework de sincronización tiene tres tipos de objetos:
sync_timeline
sync_pt
sync_fence
sync_timeline
sync_timeline
es una línea de tiempo que aumenta de forma monótona y que los proveedores deben implementar para cada instancia de controlador, como un contexto de GL, un controlador de pantalla o un blitter 2D. sync_timeline
cuenta los trabajos enviados al kernel para una pieza de hardware en particular.
sync_timeline
proporciona garantías sobre el orden de las operaciones y permite implementaciones específicas de hardware.
Sigue estos lineamientos cuando implementes sync_timeline
:
- Proporciona nombres útiles para todos los conductores, las líneas de tiempo y las barreras para simplificar la depuración.
- Implementa los operadores
timeline_value_str
ypt_value_str
en las líneas de tiempo para que la salida de depuración sea más legible. - Implementa el relleno
driver_data
para brindar a las bibliotecas del espacio del usuario, como la biblioteca de GL, acceso a los datos privados de la línea de tiempo, si lo deseas.data_driver
permite que los proveedores pasen información sobresync_fence
ysync_pts
inmutables para compilar líneas de comandos basadas en ellos. - No permite que el espacio del usuario cree o señale una barrera de forma explícita. Crear de forma explícita señales o barreras genera un ataque de denegación de servicio que detiene la funcionalidad de la canalización.
- No accedas a los elementos
sync_timeline
,sync_pt
osync_fence
de forma explícita. La API proporciona todas las funciones necesarias.
sync_pt
sync_pt
es un solo valor o punto en un sync_timeline
. Un punto tiene tres estados: activo, señalizado y error. Los puntos comienzan en el estado activo y pasan a los estados de señalización o error. Por ejemplo, cuando un consumidor de imágenes ya no necesita un búfer, se señala un sync_pt
para que un productor de imágenes sepa que puede volver a escribir en el búfer.
sync_fence
sync_fence
es una colección de valores de sync_pt
que suelen tener diferentes elementos superiores de sync_timeline
(como el controlador de pantalla y la GPU). sync_fence
, sync_pt
y sync_timeline
son los elementos primitivos principales que usan los controladores y el espacio del usuario para comunicar sus dependencias. Cuando una barrera se señala, se garantiza que todos los comandos emitidos antes de la barrera se completaron, ya que el controlador del kernel o el bloque de hardware ejecutan los comandos en orden.
El framework de sincronización permite que varios consumidores o productores indiquen cuándo terminaron de usar un búfer, y comuniquen la información de dependencia con un parámetro de función. Las vallas se respaldan con un descriptor de archivo y se pasan del espacio del kernel al espacio del usuario. Por ejemplo, una barrera puede contener dos valores de sync_pt
que indican cuándo dos consumidores de imágenes separados terminaron de leer un búfer. Cuando se señala la barrera, los productores de imágenes saben que ambos consumidores terminaron de consumir.
Las vallas, como los valores de sync_pt
, comienzan activas y cambian de estado según el estado de sus puntos. Si todos los valores de sync_pt
se marcan, sync_fence
también se marcará. Si un sync_pt
entra en un estado de error, todo el sync_fence
tendrá un estado de error.
La membresía en un sync_fence
es inmutable después de que se crea la valla. Para obtener más de un punto en una valla, se realiza una combinación en la que los puntos de dos vallas distintas se agregan a una tercera.
Si uno de esos puntos se marcó en la valla original y el otro no, la tercera valla tampoco estará en un estado marcado.
Para implementar la sincronización explícita, proporciona lo siguiente:
- Es un subsistema del espacio del kernel que implementa el framework de sincronización para un controlador de hardware en particular. Por lo general, los controladores que deben tener en cuenta las vallas son aquellos que acceden al Hardware Composer o se comunican con él.
Los archivos clave incluyen los siguientes:
- Implementación principal:
kernel/common/include/linux/sync.h
kernel/common/drivers/base/sync.c
- Documentación en
kernel/common/Documentation/sync.txt
- Biblioteca para comunicarse con el espacio del kernel en
platform/system/core/libsync
- Implementación principal:
- El proveedor debe proporcionar las barreras de sincronización adecuadas como parámetros para las funciones
validateDisplay()
ypresentDisplay()
en el HAL. - Dos extensiones de GL relacionadas con las barreras (
EGL_ANDROID_native_fence_sync
yEGL_ANDROID_wait_sync
) y compatibilidad con barreras en el controlador de gráficos.
Caso de éxito: Implementa un controlador de pantalla
Para usar la API que admite la función de sincronización, desarrolla un controlador de pantalla que tenga una función de búfer de pantalla. Antes de que existiera el framework de sincronización, esta función recibía objetos dma-buf
, colocaba esos búferes en la pantalla y se bloqueaba mientras el búfer era visible. Por ejemplo:
/* * assumes buffer is ready to be displayed. returns when buffer is no longer on * screen. */ void display_buffer(struct dma_buf *buffer);
Con el framework de sincronización, la función display_buffer
es más compleja. Mientras se muestra un búfer, este se asocia con una barrera que indica cuándo estará listo. Puedes poner en cola e iniciar el trabajo después de que se quite la barrera.
Poner en cola e iniciar el trabajo después de que se borra la barrera no bloquea nada. Devuelves inmediatamente tu propia barrera, lo que garantiza cuándo el búfer estará fuera de la pantalla. A medida que pones en cola los búferes, el kernel enumera las dependencias con el framework de sincronización:
/* * 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);
Integración de la sincronización
En esta sección, se explica cómo integrar el framework de sincronización del espacio del kernel con las partes del espacio del usuario del framework de Android y los controladores que deben comunicarse entre sí. Los objetos del espacio del kernel se representan como descriptores de archivos en el espacio del usuario.
Convenciones de integración
Sigue las convenciones de la interfaz HAL de Android:
- Si la API proporciona un descriptor de archivo que hace referencia a un
sync_pt
, el controlador del proveedor o el HAL que usa la API deben cerrar el descriptor de archivo. - Si el controlador del proveedor o el HAL pasan un descriptor de archivo que contiene un
sync_pt
a una función de la API, el controlador del proveedor o el HAL no deben cerrar el descriptor de archivo. - Para seguir usando el descriptor de archivo de barrera, el controlador del proveedor o el HAL deben duplicar el descriptor.
Un objeto de barrera se cambia de nombre cada vez que pasa por BufferQueue.
La compatibilidad con barreras del kernel permite que las barreras tengan cadenas para los nombres, por lo que el framework de sincronización usa el nombre de la ventana y el índice del búfer que se pone en cola para nombrar la barrera, como SurfaceView:0
. Esto es útil para depurar y, así, identificar la fuente de un bloqueo, ya que los nombres aparecen en el resultado de /d/sync
y en los informes de errores.
Integración de ANativeWindow
ANativeWindow reconoce las barreras. dequeueBuffer
, queueBuffer
y cancelBuffer
tienen parámetros de valla.
Integración de OpenGL ES
La integración de la sincronización de OpenGL ES se basa en dos extensiones de EGL:
EGL_ANDROID_native_fence_sync
proporciona una forma de crear o encapsular descriptores de archivos de vallas nativas de Android en objetosEGLSyncKHR
.EGL_ANDROID_wait_sync
permite que se produzcan bloqueos del lado de la GPU en lugar del lado de la CPU, lo que hace que la GPU espere aEGLSyncKHR
. La extensiónEGL_ANDROID_wait_sync
es la misma que la extensiónEGL_KHR_wait_sync
.
Para usar estas extensiones de forma independiente, implementa la extensión EGL_ANDROID_native_fence_sync
junto con la compatibilidad del kernel asociada. A continuación, habilita la extensión EGL_ANDROID_wait_sync
en tu controlador. La extensión EGL_ANDROID_native_fence_sync
consta de un tipo de objeto de valla nativa EGLSyncKHR
distinto. Como resultado, las extensiones que se aplican a los tipos de objetos EGLSyncKHR
existentes no necesariamente se aplican a los objetos EGL_ANDROID_native_fence
, lo que evita interacciones no deseadas.
La extensión EGL_ANDROID_native_fence_sync
emplea un atributo de descriptor de archivo de valla nativo correspondiente que solo se puede establecer en el momento de la creación y no se puede consultar directamente desde un objeto de sincronización existente. Este atributo se puede establecer en uno de los dos modos siguientes:
- Un descriptor de archivo de barrera válido encapsula un descriptor de archivo de barrera nativo existente de Android en un objeto
EGLSyncKHR
. - -1 crea un descriptor de archivo de valla nativo de Android a partir de un objeto
EGLSyncKHR
.
Usa la llamada a la función DupNativeFenceFD()
para extraer el objeto EGLSyncKHR
del descriptor de archivo de la barrera nativa de Android.
Esto tiene el mismo resultado que consultar el atributo set, pero se ajusta a la convención de que el destinatario cierra la barrera (por lo tanto, la operación duplicada). Por último, destruir el objeto EGLSyncKHR
cierra el atributo interno de la barrera.
Integración de Hardware Composer
El Hardware Composer controla tres tipos de barreras de sincronización:
- Las barreras de adquisición se pasan junto con los búferes de entrada a las llamadas
setLayerBuffer
ysetClientTarget
. Representan una escritura pendiente en el búfer y deben indicar antes de que SurfaceFlinger o HWC intenten leer desde el búfer asociado para realizar la composición. - Las barreras de lanzamiento se recuperan después de la llamada a
presentDisplay
con la llamada agetReleaseFences
. Representan una lectura pendiente del búfer anterior en la misma capa. Una barrera de liberación indica cuándo el HWC ya no usa el búfer anterior porque el búfer actual reemplazó al anterior en la pantalla. Las barreras de versión se devuelven a la app junto con los búferes anteriores que se reemplazarán durante la composición actual. La app debe esperar hasta que se señale una barrera de lanzamiento antes de escribir contenido nuevo en el búfer que se le devolvió. - Las vallas de presentación se muestran, una por fotograma, como parte de la llamada a
presentDisplay
. Las barreras de presentación representan el momento en que se completó la composición de este fotograma o, de forma alternativa, el momento en que ya no se necesita el resultado de la composición del fotograma anterior. En el caso de las pantallas físicas,presentDisplay
devuelve vallas presentes cuando el fotograma actual aparece en la pantalla. Después de que se devuelven las barreras de presentación, es seguro volver a escribir en el búfer de destino de SurfaceFlinger, si corresponde. En el caso de las pantallas virtuales, se devuelven las barreras de presentación cuando es seguro leer desde el búfer de salida.