Framework de sincronización

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 las primitivas de sincronización se pasen 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 dibujó 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 a procesar 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 framework de sincronización también permite que los implementadores usen 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 terminan de usar un búfer. La sincronización explícita se implementa en el espacio del kernel.

Entre los beneficios de la sincronización explícita, se incluyen los siguientes:

  • Menos variación de comportamiento entre dispositivos
  • Mejor compatibilidad con 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 monotónica y que los proveedores deben implementar para cada instancia de controlador, como un contexto 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 garantiza el orden de las operaciones y permite implementaciones específicas del hardware.

Sigue estos lineamientos cuando implementes sync_timeline:

  • Proporciona nombres útiles para todos los controladores, las líneas de tiempo y las barreras para simplificar la depuración.
  • Implementa los operadores timeline_value_str y pt_value_str en las líneas de tiempo para que el resultado de la depuración sea más legible.
  • Implementa el relleno driver_data para brindar a las bibliotecas del espacio de usuario, como la biblioteca GL, acceso a datos privados de la línea de tiempo, si es necesario. data_driver permite que los proveedores pasen información sobre sync_fence y sync_pts inmutables para compilar líneas de comandos basadas en ellos.
  • No permitas que el espacio de usuario cree o indique una barrera de forma explícita. La creación explícita de 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 o sync_fence de forma explícita. La API proporciona todas las funciones necesarias.

sync_pt

sync_pt es un valor o punto único en un sync_timeline. Un punto tiene tres estados: activo, indicado y error. Los puntos comienzan en el estado activo y pasan a los estados indicado o error. Por ejemplo, cuando un consumidor de imágenes ya no necesita un búfer, se indica 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 sync_pt que suelen tener diferentes elementos superiores sync_timeline (como para el controlador de pantalla y la GPU). sync_fence, sync_pt y sync_timeline son las primitivas principales que usan los controladores y el espacio de usuario para comunicar sus dependencias. Cuando se indica una barrera, se completan todos los comandos emitidos antes de la barrera porque 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 terminan de usar un búfer, y comuniquen la información de dependencia con un parámetro de función. Las barreras están respaldadas por un descriptor de archivo y se pasan del espacio del kernel al espacio de usuario. Por ejemplo, una barrera puede contener dos valores sync_pt que indican cuándo dos consumidores de imágenes independientes terminan de leer un búfer. Cuando se indica la barrera, los productores de imágenes saben que ambos consumidores terminaron de consumir.

Las barreras, como los valores sync_pt, comienzan activas y cambian de estado según el estado de sus puntos. Si se indican todos los valores sync_pt, se indica el sync_fence. Si un sync_pt cae en un estado de error, todo el sync_fence tiene un estado de error.

La membresía en un sync_fence es inmutable después de que se crea la barrera. Para obtener más de un punto en una barrera, se realiza una combinación en la que se agregan puntos de dos barreras distintas a una tercera. Si uno de esos puntos se indicó en la barrera de origen y el otro no, la tercera barrera tampoco estará en un estado indicado.

Para implementar la sincronización explícita, proporciona lo siguiente:

  • Un subsistema del espacio del kernel que implementa el framework de sincronización para un controlador de hardware en particular. Los controladores que deben reconocer las barreras suelen ser cualquier elemento que acceda a Hardware Composer (HWC) o se comunique con él. Los archivos clave incluyen lo siguiente:
    • 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
  • El proveedor debe proporcionar las barreras de sincronización adecuadas como parámetros para las funciones validateDisplay() y presentDisplay() en la capa de abstracción de hardware (HAL).
  • Dos extensiones GL relacionadas con barreras (EGL_ANDROID_native_fence_sync y EGL_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 estaba 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 coloca un búfer en la pantalla, el búfer se asocia con una barrera que indica cuándo estará listo. Puedes poner en cola e iniciar el trabajo después de que se borre la barrera.

Poner en cola e iniciar el trabajo después de que se borre la barrera no bloquea nada. Devuelves de inmediato tu propia barrera, que indica cuándo el búfer estará fuera de la pantalla. A medida que pones en cola los búferes, el kernel muestra 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 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 de 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 de 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 la HAL que usa la API deben cerrar el descriptor de archivo.
  • Si el controlador del proveedor o la HAL pasan un descriptor de archivo que contiene un sync_pt a una función de la API, el controlador del proveedor o la HAL no deben cerrar el descriptor de archivo.
  • Para seguir usando el descriptor de archivo de barrera, el controlador del proveedor o la HAL deben duplicar el descriptor.

Se cambia el nombre de un objeto de barrera 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 la depuración, ya que permite identificar la fuente de un interbloqueo, 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 barrera.

Integración de OpenGL ES

La integración de sincronización de OpenGL ES se basa en dos extensiones de EGL:

  • EGL_ANDROID_native_fence_sync proporciona una forma de encapsular o crear descriptores de archivos de barrera nativos de Android en objetos EGLSyncKHR.
  • EGL_ANDROID_wait_sync permite bloqueos del lado de la GPU en lugar del lado de la CPU, lo que hace que la GPU espere a EGLSyncKHR. La EGL_ANDROID_wait_sync extensión es la misma que la EGL_KHR_wait_sync extensión.

Para usar estas extensiones de forma independiente, implementa la extensión EGL_ANDROID_native_fence_sync junto con la compatibilidad con el kernel asociada. Luego, 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 EGLSyncKHR de barrera nativa distinta. Como resultado, las extensiones que se aplican a los tipos de objetos EGLSyncKHR existentes no se aplican necesariamente 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 barrera nativa correspondiente que solo se puede configurar en el momento de la creación y no se puede consultar directamente desde un objeto de sincronización existente. Este atributo se puede configurar en uno de los dos modos siguientes:

  • Un descriptor de archivo de barrera válido encapsula un descriptor de archivo de barrera nativo de Android existente en un objeto EGLSyncKHR.
  • -1 crea un descriptor de archivo de barrera nativo de Android a partir de un EGLSyncKHR objeto.

Usa la llamada a la función DupNativeFenceFD() para extraer el objeto EGLSyncKHR del descriptor de archivo de barrera nativo de Android. Esto tiene el mismo resultado que consultar el atributo establecido, 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 de barrera interno.

Integración de Hardware Composer

El HWC controla tres tipos de barreras de sincronización:

  • Las barreras de adquisición se pasan junto con los búferes de entrada a las setLayerBuffer y setClientTarget llamadas. Representan una escritura pendiente en el búfer y deben indicarse antes de que el SurfaceFlinger o el HWC intenten leer desde el búfer asociado para realizar la composición.
  • Las **barreras de liberación** se recuperan después de la llamada a presentDisplay con la llamada getReleaseFences. 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 liberación se vuelven a pasar 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 indique una barrera de liberación antes de escribir contenido nuevo en el búfer que se le devolvió.
  • Las barreras de presentación se muestran, una por fotograma, como parte de la llamada a presentDisplay. Las barreras de presentación representan cuándo se completó la composición de este fotograma o, de forma alternativa, cuándo ya no se necesita el resultado de la composición del fotograma anterior. En el caso de las pantallas físicas, presentDisplay muestra barreras de presentación cuando aparece el fotograma actual en la pantalla. Después de que se muestran 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, las barreras de presentación se muestran cuando es seguro leer desde el búfer de salida.