Biblioteca de Frame Pacing   Parte de Android Game Development Kit.

La biblioteca de Android Frame Pacing, también conocida como Swappy, es parte de las bibliotecas de AGDK. Permite que los juegos de OpenGL y Vulkan logren un procesamiento eficaz y un ritmo de fotogramas correcto en Android. En este documento, se define el ritmo de los fotogramas, se describen las situaciones en las que este resulta necesario y se muestra cómo la biblioteca se encarga de estas situaciones. Si deseas ir directamente a la implementación del ritmo de fotogramas en tu juego, consulta el Siguiente paso.

Información general

El ritmo de fotogramas es la sincronización de la lógica y el bucle de procesamiento de un juego con el subsistema de visualización del SO y el hardware de visualización subyacente. El subsistema de visualización de Android se diseñó a fin de evitar artefactos visuales (conocidos como seccionamientos) que pueden ocurrir cuando el hardware de visualización cambia de fotograma a mitad de una actualización. Para evitar estos artefactos, el subsistema de visualización hace lo siguiente:

  • Almacena en búfer los fotogramas anteriores de forma interna
  • Detecta envíos tardíos de fotogramas
  • Cuando se detectan fotogramas tardíos, repite la visualización de los anteriores

Un juego avisa a SurfaceFlinger, el compositor del subsistema de visualización, que envió todas las llamadas de dibujo que necesita un fotograma (llamando a eglSwapBuffers ovkQueuePresentKHR). SurfaceFlinger indica la disponibilidad de un fotograma al hardware de visualización mediante un bloqueo. Luego, este hardware muestra el fotograma especificado. El hardware de visualización marca a una frecuencia constante, por ejemplo, 60 Hz. Si no hay un fotograma nuevo cuando el hardware necesita uno, este vuelve a mostrar el anterior.

La latencia de fotogramas errática suele ocurrir cuando se procesa un bucle de procesamiento del juego a una frecuencia diferente de la del hardware de visualización nativo. Si un juego que se ejecuta a 30 FPS intenta procesarse en un dispositivo que admite 60 FPS de manera nativa, el bucle de procesamiento del juego no sabrá que un fotograma repetido permanecerá en la pantalla durante 16 milisegundos más. Esta desconexión, por lo general, genera una inconsistencia considerable en la latencia de fotogramas, como 49 milisegundos, 16 milisegundos y 33 milisegundos. Las escenas demasiado complejas complican aún más este problema, ya que provocan la pérdida de fotogramas.

Soluciones no óptimas

En el pasado, se aplicaron las siguientes soluciones relativas al ritmo de los fotogramas en los juegos y se observó que, por lo general, producen una latencia de fotogramas errática y aumentan la latencia de entrada.

Enviar fotogramas tan rápido como la API de procesamiento lo permita

Este enfoque vincula un juego a la actividad SurfaceFlinger variable e introduce un fotograma adicional de latencia. La canalización de pantalla contiene una fila de fotogramas, por lo general de tamaño 2, que se llena si el juego intenta presentar fotogramas muy deprisa. Como no hay más espacio en la fila, una llamada de OpenGL o Vulkan bloquea el bucle de juego (o, al menos, el subproceso de procesamiento). Por lo tanto, el juego se ve obligado a esperar a que el hardware de visualización muestre un fotograma, y esta contrapresión sincroniza los dos componentes. Esta situación se conoce como exceso en búfer o exceso en fila. El proceso del procesador no detecta lo que sucede, por lo que la velocidad de los fotogramas se torna más errática. Si el juego toma muestras de entrada antes del fotograma, la latencia de entrada empeora.

Usar Android Choreographer por sí solo

Los juegos también usan Android Choreographer para la sincronización. Este componente, disponible en Java desde el nivel de API 16 y en C++ desde el nivel de API 24, entrega marcas regulares con la misma frecuencia que el subsistema de visualización. Aún así, hay algunos detalles respecto de cuándo se entrega esta marca en relación con el VSYNC real del hardware, y estos desfases varían según el dispositivo. El exceso en búfer puede seguir ocurriendo en casos de fotogramas prolongados.

Ventajas de la biblioteca de Frame Pacing

La biblioteca de Frame Pacing usa Android Choreographer para la sincronización y se encarga de la variabilidad de la entrega de marcas. Usa marcas de tiempo de presentación a fin de garantizar que los fotogramas se presenten en el momento correcto y sincroniza vallas para evitar el exceso en búfer. La biblioteca usa el Choreographer del NDK si está disponible y recurre a Choreographer de Java cuando no lo está.

La biblioteca controla varias frecuencias de actualización si el dispositivo las admite, lo que le proporciona al juego una mayor flexibilidad a la hora de presentar un fotograma. Por ejemplo, en un dispositivo que admite tanto una frecuencia de actualización de 60 Hz como una de 90 Hz, un juego que no puede producir 60 fotogramas por segundo puede disminuir a 45 FPS en lugar de 30 FPS para que continúe sin problemas. La biblioteca detecta la velocidad de fotogramas esperada del juego y ajusta automáticamente los tiempos de presentación de fotogramas según corresponda.

Cómo funciona

En las siguientes secciones, se muestra cómo la biblioteca de Frame Pacing maneja los fotogramas largos y cortos de los juegos a fin de lograr el ritmo de fotogramas correcto.

Ritmo de fotogramas correcto a 30 Hz

En la Figura 1, se muestra la situación ideal en Android cuando se procesa a 30 Hz en un dispositivo que admite 60 Hz. SurfaceFlinger se conecta a nuevos búferes gráficos, si están presentes (observa que en el diagrama se indica que "no hay búfer" presente y el anterior se repite).

Ritmo de fotogramas ideal de 30 Hz en un dispositivo que admite 60 Hz

Figura 1: Ritmo de fotogramas ideal de 30 Hz en un dispositivo que admite 60 Hz

Los fotogramas de juego cortos producen saltos

En la mayoría de los dispositivos modernos, los motores de juegos dependen de que el Choreographer de la plataforma entregue las marcas para impulsar el envío de los fotogramas. Sin embargo, aún existe la posibilidad de tener un ritmo de fotogramas malo a causa de los fotogramas cortos, como se ve en la Figura 2. El jugador percibe como saltos los fotogramas cortos seguidos de los largos.

Fotogramas de juego cortos

Figura 2: El fotograma C de juego corto hace que el fotograma B presente solo un fotograma, seguido de varios fotogramas C

La biblioteca de Frame Pacing resuelve esto usando las marcas de tiempo de presentación. La biblioteca usa las extensiones de marca de tiempo de presentación EGL_ANDROID_presentation_time y VK_GOOGLE_display_timing para que no se presenten los fotogramas antes de tiempo, como se ve en la Figura 3.

Marcas de tiempo de presentación

Figura 3: fotograma B de juego presentado dos veces para una visualización más fluida

Los fotogramas largos producen saltos y latencia

Cuando la carga de trabajo de visualización tarda más que la carga de trabajo de la aplicación, se agregan fotogramas adicionales a una fila. Una vez más, esto genera un salto y también puede provocar un fotograma adicional de latencia debido al exceso en búfer (consulta la Figura 4). La biblioteca quita tanto los saltos como el fotograma adicional de latencia.

Fotogramas de juego largos

Figura 4: el fotograma B largo genera un ritmo incorrecto para 2 fotogramas: A y B

La biblioteca resuelve este problema con vallas de sincronización (EGL_KHR_fence_sync y VkFence) que insertan esperas en la aplicación que permiten que se actualice la canalización de pantalla, en lugar de permitir la generación de contrapresión. El fotograma A aún presenta un fotograma adicional, pero ahora el fotograma B se presenta correctamente, como se ve en la Figura 5.

Se agregaron esperas a la capa de la aplicación

Figura 5: los fotogramas C y D esperan para presentarse

Modos de operación compatibles

Puedes configurar la biblioteca de Frame Pacing para que funcione en uno de los tres modos siguientes:

  • Modo automático desactivado + canalización
  • Modo automático activado + canalización
  • Modo automático activado + modo de canalización automático (con o sin canalización)

Puedes experimentar con el modo automático y los modos de canalización, pero, para comenzar, debes desactivarlos y, luego, incluir lo siguiente después de inicializar Swappy:

  swappyAutoSwapInterval(false);
  swappyAutoPipelineMode(false);
  swappyEnableStats(false);
  swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);

Modo de canalización

A fin de coordinar las cargas de trabajo del motor, la biblioteca suele usar un modelo de canalización que separa las cargas de trabajo de la CPU y de la GPU en los límites de VSYNC.

Modo de canalización

Figura 6: modo de canalización

Modo sin canalización

En general, este enfoque da como resultado una latencia de entrada en la pantalla más baja y predecible. En los casos en que un juego tenga una latencia de fotogramas muy baja, tanto las cargas de trabajo de la CPU como las de la GPU podrían caber en un intervalo de intercambio único. De ser así, un enfoque sin canalización entregaría una latencia de entrada en la pantalla más baja.

Modo sin canalización

Figura 7: modo sin canalización

Modo automático

En la mayoría de los juegos, no se sabe cómo elegir el intervalo de intercambio, que es el tiempo durante el cual se presenta cada fotograma (por ejemplo, 33.3 ms para 30 Hz). En algunos dispositivos, un juego puede procesarse a 60 FPS mientras que, en otro, podría ser necesario disminuir ese valor. El modo automático mide los tiempos de CPU y GPU para hacer lo siguiente:

  • Seleccionar automáticamente los intervalos de intercambio: los juegos que entregan 30 Hz en algunas escenas y 60 Hz en otras pueden permitir que la biblioteca ajuste este intervalo de forma dinámica.
  • Desactivar la canalización de fotogramas ultrarrápidos: esto entrega una latencia de entrada en la pantalla óptima en todos los casos.

Varias frecuencias de actualización

Los dispositivos que admiten varias frecuencias de actualización proporcionan mayor flexibilidad a la hora de elegir un intervalo de intercambio que logre fluidez:

  • En dispositivos de 60 Hz: 60 FPS/30 FPS/20 FPS
  • En dispositivos de 60 Hz y 90 Hz: 90 FPS/60 FPS/45 FPS/30 FPS
  • En dispositivos de 60 Hz, 90 Hz y 120 Hz: 120 FPS/90 FPS/60 FPS/45 FPS/40 FPS/30 FPS

La biblioteca elige la frecuencia de actualización que mejor se adapte a la duración del procesamiento real de los fotogramas del juego, lo que brinda una mejor experiencia visual.

Para obtener más información sobre el ritmo de fotogramas de frecuencia de actualización múltiple, consulta la entrada de blog Procesamiento a una frecuencia de actualización alta en Android.

Estadísticas de fotogramas

La biblioteca de Frame Pacing ofrece las siguientes estadísticas para la depuración y la generación de perfiles:

  • Un histograma de la cantidad de actualizaciones de pantalla que un fotograma esperó en la fila del compositor una vez completado el procesamiento
  • Un histograma de la cantidad de actualizaciones de pantalla que se pasaron entre el momento de presentación solicitado y el momento de presentación real
  • Un histograma de la cantidad de actualizaciones de pantalla que se pasaron entre dos fotogramas consecutivos
  • Un histograma de la cantidad de actualizaciones de pantalla que se pasaron entre el inicio del trabajo de la CPU para este fotograma y el momento de presentación real

Siguiente paso

Consulta cualquiera de las siguientes guías para integrar la biblioteca de Android Frame Pacing a tu juego: