Android triple buffering – comportamiento esperado?

Estoy investigando el rendimiento de mi aplicación, ya que me di cuenta de que cayó algunos marcos durante el desplazamiento. Corrí systrace (en un Nexus 4 corriendo 4.3) y noté una sección interesante en la salida.

Todo está bien al principio. Zoom en la sección izquierda , podemos ver que el dibujo comienza en cada vsync, termina con tiempo de sobra, y espera hasta el próximo vsync. Dado que es triple buffer, debe dibujar en un búfer que se publicará en el siguiente vsync después de que se hace.

En el 4º vsync en la captura de pantalla ampliada, la aplicación funciona y la operación de dibujo no finaliza a tiempo para el siguiente vsync. Sin embargo, no eliminamos ninguna trama porque los dibujos anteriores estaban trabajando un marco por delante.

Después de esto sucede sin embargo, las operaciones de dibujo no compensan el vsync perdido. En su lugar, sólo una operación de dibujo comienza por vsync, y ahora no están dibujando un marco adelante más.

Zoom en la sección de la derecha , la aplicación hace un poco más de trabajo y se pierde otro vsync. Puesto que no estábamos dibujando un marco adelante, un marco realmente se deja caer aquí. Después de esto, se remonta a dibujar un fotograma por delante.

¿Es este comportamiento esperado? Mi comprensión era que el almacenar intermediario triple permitió que usted recuperara si usted faltó a un vsync, pero este comportamiento parece que cae un marco una vez cada dos vsyncs que usted falta.


Preguntas de seguimiento

  1. En la parte derecha de esta captura de pantalla , la aplicación está procesando buffers más rápido de lo que la pantalla los está consumiendo. Durante realizarTraversals # 1 (etiquetado en la captura de pantalla), digamos que se está visualizando el buffer A y se está procesando el buffer B. # 1 termina mucho antes del vsync y pone el búfer B en la cola. En este punto, ¿no debería la aplicación ser capaz de iniciar inmediatamente el renderizado de la memoria intermedia C? En su lugar, performTraversals # 2 no se inicia hasta el próximo vsync, perdiendo el valioso tiempo entre ellos.

  2. En una vena similar, estoy un poco confundido acerca de la necesidad de waitForever en el lado izquierdo aquí . Digamos que se está mostrando el búfer A, el búfer B está en la cola y el búfer C está siendo procesado. Cuando el búfer C está finalizado, ¿por qué no se agrega inmediatamente a la cola? En su lugar, hace un waitForever hasta que el búfer B se elimina de la cola, momento en el que agrega el búfer C, por lo que la cola parece estar siempre en el tamaño 1, no importa lo rápido que la aplicación esté procesando buffers.

La cantidad de almacenamiento en búfer proporcionado sólo importa si mantiene los búferes llenos. Eso significa que el renderizado más rápido que la pantalla los está consumiendo.

Las etiquetas no aparecen en las imágenes, pero supongo que la fila púrpura sobre la fila verde vsync es el estado BufferQueue. Puede ver que generalmente tiene 0 o 1 buffers completos en cualquier momento. A la izquierda de la imagen "ampliada a la izquierda" se puede ver que tiene dos búferes, pero después de eso sólo tiene uno, y 3/4 del camino a través de la pantalla se ve una barra púrpura muy corta la Indica que acaba de representar el marco en el tiempo.

Vea este post y este post como fondo.

Actualización para las preguntas añadidas …

El detalle en el otro poste apenas arañó la superficie. Debemos profundizar.

El número de BufferQueue mostrado en systrace es el número de buffers en cola, es decir, el número de buffers que tienen contenido en ellos. Cuando SurfaceFlinger agarra un búfer para mostrarlo, libera el búfer inmediatamente, cambiando su estado a "libre". Esto es particularmente emocionante cuando se muestra el búfer en una superposición, porque la visualización se representa directamente desde el búfer (en contraposición a la composición en un búfer de borrador y mostrarlo).

Permítanme decirlo de nuevo: el búfer desde el cual la pantalla está leyendo activamente los datos para su visualización en la pantalla está marcado como "libre" en el BufferQueue. El búfer tiene una valla asociada que es inicialmente "activa". Mientras está activo, nadie está autorizado a modificar el contenido del búfer. Cuando la pantalla ya no necesita el búfer, señala la valla.

Así que la razón por la que el código a la izquierda de su rastreo está en waitForever() es porque está a la espera de la valla para señalar. Cuando VSYNC llega, la pantalla cambia a un búfer diferente, señala la valla, y su aplicación puede comenzar a usar el búfer inmediatamente. Esto elimina la latencia que se produciría si tuviera que esperar a que SurfaceFlinger se despierte, ver que el búfer ya no estaba en uso, enviar un IPC a través de BufferQueue para liberar el búfer, etc.

Tenga en cuenta que las llamadas a waitForever() sólo aparecen cuando no se está cayendo detrás (lado izquierdo y lado derecho de la traza). No estoy seguro de improviso por qué está sucediendo en absoluto cuando la cola tiene sólo 1 buffer completo – debe ser dequeueing el más antiguo de amortiguación, que ya debería haber señalado.

La conclusión es que nunca verá el BufferQueue ir por encima de dos para el almacenamiento temporal triple.

No todos los dispositivos funcionan como se describe anteriormente. Nexus 7 (2012) no utiliza el mecanismo de "sincronización explícita", y los dispositivos pre-ICS no tienen BufferQueues en absoluto.

Volviendo a su captura de pantalla numerada, sí, hay mucho tiempo entre '1' y '2' donde su aplicación podría ejecutar performTraversals (). Es difícil decir con certeza sin saber lo que está haciendo su aplicación, pero me imagino que tienes un ciclo de animación impulsado por Choreographer que despierta cada VSYNC y funciona. No funciona más a menudo que eso.

Si sincroniza Android Breakout , puede ver lo que parece cuando renderiza lo más rápido posible ("cola de relleno") y confía en la presión de retorno de BufferQueue para regular la velocidad del juego.

Es especialmente interesante comparar N4 corriendo 4.3 con N4 que funciona 4.4. En 4,3, la traza es similar a la tuya, con la cola en su mayor parte flotando a 1, con gotas regulares a 0 y picos ocasionales a 2. En 4.4, la cola es casi siempre a 2 con una caída ocasional a 1. En ambos casos es eglSwapBuffers() en eglSwapBuffers() ; En 4.3 la traza generalmente muestra waitForever() debajo de eso, mientras que en 4.4 muestra dequeueBuffer() . (No sé la razón de esto de improviso.)

Actualización 2: La razón de la diferencia entre 4,3 y 4,4 parece ser un cambio de controlador Nexus 4. El controlador 4.3 utilizó la llamada dequeueBuffer antigua, que se convierte en dequeueBuffer_DEPRECATED() ( Surface.cpp línea 112 ). La interfaz antigua no toma la valla como un parámetro "out", por lo que la llamada tiene que llamar a waitForever() sí. La nueva interfaz acaba de devolver la valla al controlador GL, que hace la espera cuando es necesario (que puede no ser de inmediato).

Actualización 3: Una explicación aún más larga ya está disponible aquí .

  • Compresión de imagen de Android sin carga completa en la memoria
  • ¿Cuál es la mejor práctica de almacenar imágenes en android en la tarjeta SD o en SQL lite DB?
  • Mi aplicación se desplaza muy mal después de importar la fuente roboto en la carpeta assets / fonts
  • Simular ancho de banda bajo en android
  • Buenas prácticas para ordenar una lista con una de las cuatro reglas
  • Los valores de caché de SharedPreferences tienen sentido?
  • Throwable: No se pudo crear la tarjeta SD
  • No se puede ejecutar "ANY" reaccionar proyecto ejemplo nativo
  • ¿La codificación dura de la cadena afecta el rendimiento?
  • Cómo obtener rápidamente el ancho y la altura de TextView utilizando Paint.getTextBounds ()?
  • ¿Por qué android: singleLine = "true" hace el desplazamiento ListView muy laggy?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.