Marco de sincronización

El marco de sincronización describe explícitamente las dependencias entre diferentes operaciones asincrónicas en el sistema de gráficos de Android. El marco proporciona una API que permite a los componentes indicar cuándo se liberan los buffers. El marco también permite pasar primitivas de sincronización entre controladores desde el kernel al espacio de usuario y entre los propios procesos del espacio de usuario.

Por ejemplo, una aplicación puede poner en cola trabajos para realizar en la GPU. La GPU comienza a dibujar esa imagen. Aunque la imagen aún no se ha dibujado en la memoria, el puntero del búfer se pasa al compositor de la ventana junto con una valla 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 la GPU finaliza, el controlador de pantalla muestra inmediatamente la imagen.

El marco de sincronización también permite a los implementadores aprovechar los recursos de sincronización en sus propios componentes de hardware. Finalmente, el marco proporciona visibilidad del proceso de gráficos para ayudar con la depuración.

Sincronización explícita

La sincronización explícita permite a los productores y consumidores de búferes de gráficos indicar cuando han terminado de usar un búfer. La sincronización explícita se implementa en el espacio del kernel.

Los beneficios de la sincronización explícita incluyen:

  • Menos variación de comportamiento entre dispositivos
  • Mejor soporte de depuración
  • Métricas de prueba mejoradas

El marco de sincronización tiene tres tipos de objetos:

  • sync_timeline
  • sync_pt
  • sync_fence

línea de tiempo_sincronización

sync_timeline es una línea de tiempo que aumenta monótonamente 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 proporciona garantías sobre el orden de las operaciones y permite implementaciones específicas de hardware.

Siga estas pautas al implementar sync_timeline :

  • Proporcione nombres útiles para todos los controladores, líneas de tiempo y barreras para simplificar la depuración.
  • Implemente 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.
  • Implemente 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 lo desea. data_driver permite a los proveedores pasar información sobre los inmutables sync_fence y sync_pts para crear líneas de comando basadas en ellos.
  • No permita que el espacio de usuario cree o señale explícitamente una valla. La creación explícita de señales/vallas da como resultado un ataque de denegación de servicio que detiene la funcionalidad del canal.
  • No acceda a los elementos sync_timeline , sync_pt o sync_fence explícitamente. La API proporciona todas las funciones necesarias.

sincronización_pt

sync_pt es un valor único o punto en sync_timeline . Un punto tiene tres estados: activo, señalizado y error. Los puntos comienzan en el estado activo y pasan al estado de señal o de 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 está bien escribir en el búfer nuevamente.

valla_sincronización

sync_fence es una colección de valores sync_pt que a menudo tienen diferentes padres sync_timeline (como para el controlador de pantalla y la GPU). sync_fence , sync_pt y sync_timeline son las principales primitivas que utilizan los controladores y el espacio de usuario para comunicar sus dependencias. Cuando se señala una barrera, se garantiza que todos los comandos emitidos antes de la barrera estarán completos porque el controlador del núcleo o el bloque de hardware ejecuta los comandos en orden.

El marco de sincronización permite que varios consumidores o productores indiquen cuando han terminado de usar un búfer, comunicando 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 valla puede contener dos valores sync_pt que indican cuando dos consumidores de imágenes independientes terminan de leer un búfer. Cuando se señala la valla, los productores de imágenes saben que ambos consumidores han terminado de consumir.

Las vallas, como los valores sync_pt , comienzan activas y cambian de estado según el estado de sus puntos. Si se señalan todos los valores sync_pt , se señala sync_fence . Si un sync_pt cae en un estado de error, todo sync_fence tendrá un estado de error.

La membresía en 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 donde los puntos de dos vallas distintas se agregan a una tercera valla. Si uno de esos puntos estaba señalizado en la valla de origen y el otro no, la tercera valla tampoco estará en estado señalizado.

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

  • Un subsistema de espacio de kernel que implementa el marco de sincronización para un controlador de hardware en particular. Los controladores que deben tener en cuenta las barreras generalmente son cualquier cosa que acceda o se comunique con Hardware Composer. Los archivos clave incluyen:
    • Implementación central:
      • 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 los límites de sincronización adecuados como parámetros para las funciones validateDisplay() y presentDisplay() en HAL.
  • Dos extensiones GL relacionadas con vallas ( EGL_ANDROID_native_fence_sync y EGL_ANDROID_wait_sync ) y compatibilidad con vallas en el controlador de gráficos.

Estudio de caso: implementar un controlador de pantalla

Para utilizar la API que admite la función de sincronización, desarrolle un controlador de pantalla que tenga una función de búfer de visualización. Antes de que existiera el marco de sincronización, esta función recibía objetos dma-buf , colocaba esos búferes en la pantalla y los 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 marco de sincronización, la función display_buffer es más compleja. Mientras se muestra un búfer, el búfer está asociado con una valla que indica cuándo estará listo el búfer. Puede hacer cola e iniciar el trabajo después de que se despeje la valla.

Hacer cola e iniciar el trabajo después de que se despeja la valla no bloquea nada. Inmediatamente devuelve su propia valla, lo que garantiza cuándo el búfer estará fuera de la pantalla. A medida que pone en cola los buffers, el kernel enumera las dependencias con el marco 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

Esta sección explica cómo integrar el marco de sincronización del espacio del kernel con partes del espacio de usuario del marco 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

Siga las convenciones de la interfaz HAL de Android:

  • Si la API proporciona un descriptor de archivo que hace referencia a sync_pt , el controlador del proveedor o el HAL que utiliza la API debe cerrar el descriptor de archivo.
  • Si el controlador del proveedor o HAL pasa un descriptor de archivo que contiene un sync_pt a una función API, el controlador del proveedor o HAL no deben cerrar el descriptor de archivo.
  • Para continuar usando el descriptor del archivo de valla, el controlador del proveedor o el HAL deben duplicar el descriptor.

Un objeto de valla cambia de nombre cada vez que pasa por BufferQueue. La compatibilidad con la valla del kernel permite que las vallas tengan cadenas para los nombres, por lo que el marco de sincronización utiliza el nombre de la ventana y el índice del búfer que se está en cola para nombrar la valla, como SurfaceView:0 . Esto es útil en la depuración para identificar el origen de un punto muerto, ya que los nombres aparecen en la salida de /d/sync y en los informes de errores.

Integración de una ventana nativa

ANativeWindow reconoce las vallas. dequeueBuffer , queueBuffer y cancelBuffer tienen parámetros de valla.

Integración con OpenGL ES

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

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

Para utilizar estas extensiones de forma independiente, implemente la extensión EGL_ANDROID_native_fence_sync junto con el soporte del kernel asociado. A continuación, habilite la extensión EGL_ANDROID_wait_sync en su controlador. La extensión EGL_ANDROID_native_fence_sync consta de un tipo de objeto EGLSyncKHR de valla nativa distinta. 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 descriptor de archivo de valla nativo 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 dos modos:

  • Un descriptor de archivo de valla válido envuelve un descriptor de archivo de valla 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 .

Utilice la llamada a la función DupNativeFenceFD() para extraer el objeto EGLSyncKHR del descriptor de archivo de valla nativo de Android. Esto tiene el mismo resultado que consultar el atributo establecido, pero se adhiere a la convención de que el destinatario cierra la valla (de ahí la operación duplicada). Finalmente, al destruir el objeto EGLSyncKHR se cierra el atributo de valla interna.

Integración del compositor de hardware

Hardware Composer maneja 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 y setClientTarget . Estos representan una escritura pendiente en el búfer y deben enviar una señal antes de que SurfaceFlinger o HWC intente 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 mediante la llamada getReleaseFences . Estos representan una lectura pendiente del búfer anterior en la misma capa. Una valla de liberación señala cuando el HWC ya no está usando el búfer anterior porque el búfer actual ha reemplazado al búfer anterior en la pantalla. Los límites de liberación se devuelven a la aplicación junto con los búferes anteriores que se reemplazarán durante la composición actual. La aplicación debe esperar hasta que una valla de liberación indique antes de escribir nuevos contenidos en el búfer que se les devolvió.
  • Las vallas actuales se devuelven, una por cuadro, como parte de la llamada a presentDisplay . Las vallas actuales representan cuando la composición de este cuadro se ha completado o, alternativamente, cuando el resultado de la composición del cuadro anterior ya no es necesario. Para pantallas físicas, presentDisplay devuelve las barreras presentes cuando el fotograma actual aparece en la pantalla. Una vez devueltas las barreras actuales, es seguro volver a escribir en el búfer de destino de SurfaceFlinger, si corresponde. Para pantallas virtuales, las barreras presentes se devuelven cuando es seguro leer desde el búfer de salida.