¿Cuánto tiempo tarda OpenGL en actualizar realmente la pantalla?

9

Tengo una aplicación de prueba de OpenGL simple en C que dibuja cosas diferentes en respuesta a la entrada clave. (Mesa 8.0.4, probado con Mesa-EGL y con GLFW, Ubuntu 12.04LTS en una PC con NVIDIA GTX650). Los sorteos son bastante simples / rápidos (tipo de cosas de triángulo giratorio). Mi código de prueba no limita la velocidad de fotogramas deliberadamente de ninguna manera, simplemente se ve así:

while (true)
{
    draw();
    swap_buffers();
}

He cronometrado esto con mucho cuidado, y encuentro que el tiempo de una eglSwapBuffers()(o la glfwSwapBuffersmisma) llamada a la siguiente es ~ 16.6 milisegundos. El tiempo desde después de una llamada hasta eglSwapBuffers()justo antes de la próxima llamada es solo un poco menor que eso, aunque lo que se dibuja es muy simple. El tiempo que demora la llamada de las memorias intermedias de intercambio es muy inferior a 1 ms.

Sin embargo, el tiempo desde que la aplicación cambia lo que está dibujando en respuesta a la pulsación de la tecla al cambio que aparece en la pantalla es> 150 ms (aproximadamente 8-9 cuadros). Esto se mide con una grabación de la cámara de la pantalla y el teclado a 60 fps.

Por lo tanto, las preguntas:

  1. ¿Dónde se almacenan los sorteos entre una llamada para intercambiar buffers y aparecer en la pantalla? ¿Por qué la demora? Parece que la aplicación está dibujando muchos cuadros por delante de la pantalla en todo momento.

  2. ¿Qué puede hacer una aplicación OpenGL para provocar un sorteo inmediato en la pantalla? (es decir, sin almacenamiento en búfer, solo bloquee hasta que se complete el dibujo; no necesito un alto rendimiento, necesito una baja latencia)

  3. ¿Qué puede hacer una aplicación para que el sorteo inmediato anterior suceda lo más rápido posible?

  4. ¿Cómo puede una aplicación saber qué está realmente en la pantalla en este momento? (O, ¿cuánto tiempo / cuántos cuadros tiene el retraso de almacenamiento en búfer actual?)

Alex I
fuente
¿Puedes separar el tiempo que tarda el teclado en notificar tu aplicación al tiempo que lleva realmente procesar eso?
ThorinII
@ThorinII: Punto justo. Probablemente pueda configurar algo hacky usando un LED en un puerto paralelo, etc. para obtener una indicación de cuándo la aplicación realmente presiona la tecla. Aún así, es solo fgetc (stdin), ¿qué tan lento puede ser? : /
Alex I
Tenía la impresión de que glFlush se usaba para causar una descarga inmediata de todos los comandos.
Programmdude
1
@AlexI revisa este gamedev.stackexchange.com/questions/66543/… podría ayudarte
concept3d
1
@ concept3d: Sí, básicamente se responde, solo pensé que te gustaría algún representante adicional :) Aparentemente, tengo que esperar un día para otorgarlo.
Alex I

Respuestas:

6

Cualquier función API de dibujo llamada desde la CPU se enviará al búfer de anillo de comando de la GPU para que la GPU la ejecute más tarde. Esto significa que las funciones de OpenGL son principalmente funciones sin bloqueo. Entonces la CPU y la GPU trabajarán en paralelo.

Lo más importante a tener en cuenta es que su aplicación puede estar vinculada a la CPU o GPU. una vez que llame a glFinish, la CPU debería esperar a que la GPU complete sus comandos de dibujo, si la GPU está tomando más tiempo y puede / está causando que la CPU se detenga, entonces sus aplicaciones están vinculadas a la GPU. Si la GPU termina de dibujar comandos y la CPU está tardando demasiado en terminar, entonces su aplicación está vinculada a la CPU.

Y tenga en cuenta que hay una diferencia entre glFlushy glFinish.

glFlush: indica que todos los comandos que se han enviado previamente al GL deben completarse en tiempo finito.

glFinish: obliga a completar todos los comandos GL anteriores. "Finalizar no regresa hasta que todos los efectos de los comandos emitidos previamente en el cliente GL y el estado del servidor y el framebuffer se realicen completamente".

glXSwapBuffers realiza un glFlush implícito antes de que regrese. Los comandos subsiguientes de OpenGL pueden emitirse inmediatamente después de llamar a glXSwapBuffers, pero no se ejecutan hasta que se completa el intercambio del búfer.

El tiempo de trama real probablemente estará determinado por cuál de las dos CPU / GPU está tomando más tiempo para completar su trabajo.

concepto3d
fuente
Esto es muy útil. Algunas correcciones posibles: opengl.org/sdk/docs/man2/xhtml/glXSwapBuffers.xml "glXSwapBuffers realiza un glFlush implícito antes de que regrese", parece que esto realmente no hace un glFinish, por lo que el búfer de comandos puede tener bastante cosas en él cuando regresa el intercambio de buffers.
Alex I
... Además, creo que el punto más interesante es que mi aplicación de prueba muy simple no es ni CPU ni GPU limitada, ni tiene mucho trabajo que hacer aquí, pero algo más, de alguna manera termina con una baja tasa de fotogramas (exactamente igual que el monitor frecuencia de actualización ??) y alta latencia (debido al búfer de comandos, en realidad explicaste esa parte bastante bien).
Alex I
@AlexI both low framerate (exactly same as monitor refresh rate??no a menos que esté utilizando explícitamente VSync.
concept3d
@AlexI También quiero señalar que el tiempo de fotograma es diferente de FPS, use el tiempo de fotograma para perfilar su aplicación porque es lineal. FPS, por otro lado, es una medición pobre que no es lineal.
concept3d
No estoy usando explícitamente vsync. No estoy haciendo nada para cambiar la configuración predeterminada de vsync, ni siquiera sé dónde cambiarlos en OpenGL. Solo obtengo exactamente 60 fps (dentro de un porcentaje).
Alex I
4

OpenGL nunca actualiza la pantalla, técnicamente.

Hay una API del sistema de ventanas que está separada de GL (por ejemplo, GLX, WGL, CGL, EGL) que hace esto. Los intercambios de búfer que usan estas API generalmente invocan implícitamente, glFlush (...)pero en algunas implementaciones (por ejemplo, el rasterizador GDI en Windows) hace un completo glFinish (...):

 

    * En el lado izquierdo, se encuentra la ruta del ICD (hardware) SwapBuffers (...). En el lado derecho, la ruta GDI (software).

Si tiene VSYNC habilitado y doble búfer, entonces cualquier comando que modifique el búfer posterior antes de que ocurra un intercambio pendiente debe detenerse hasta el intercambio. La cola de comandos tiene una profundidad limitada, por lo que este comando bloqueado eventualmente provocará un atasco de tráfico en la tubería. Esto podría significar que, en lugar de bloquear SwapBuffers (...)su aplicación, en realidad bloquea algunos comandos GL no relacionados hasta que VBLANK llegue. Lo que realmente se reduce a cuántos buffers de respaldo tiene en su cadena de intercambio.

Siempre y cuando todos los buffers posteriores estén llenos de marcos terminados que aún no se hayan movido al frente, los buffers de intercambio provocarán implícitamente el bloqueo. Lamentablemente, no hay forma de controlar explícitamente el número de buffers de respaldo utilizados por la mayoría de las API del sistema de ventanas GL (aparte de 0 de un solo buffer o 1 de doble buffer). El controlador es libre de usar 2 buffers posteriores si lo desea (triple buffering), pero no puede solicitarlo a nivel de aplicación usando algo como GLX o WGL.

Andon M. Coleman
fuente
Andon: Buena información, gracias. Creo que lo que estoy viendo es en parte doble búfer, pero: "el intento de modificar el búfer posterior antes de que ocurra el intercambio debe bloquear" - ¿por qué? parece que todo se puede enviar al búfer de comandos, incluido el intercambio :)
Alex I
"controla explícitamente el número de memorias intermedias usadas por la mayoría de las API del sistema de ventanas GL (aparte de 0 de memoria intermedia simple o 1 de memoria intermedia doble)" - ¿cómo se controla una memoria intermedia única / doble?
Alex I
@AlexI: Aclaré el lenguaje con respecto a por qué el comando puede conducir al bloqueo. En otras API hay un concepto conocido como render-ahead, que es efectivamente la profundidad de la cola de comandos. En cuanto al control de búfer simple / doble, eso se controla mediante el formato de píxel que seleccione cuando cree su contexto de representación. En WGL, puede seleccionar búfer simple o doble, pero si selecciona doble búfer, el controlador podría crear 2 búferes posteriores (por lo tanto, el doble búfer se convierte en triple búfer) si el usuario configura su controlador para hacer esto.
Andon M. Coleman
En realidad, no desea renderizar demasiados cuadros por delante, ya que si bien esto reducirá el bloqueo, también aumenta la latencia. La mejor manera de minimizar la latencia es invocar glFinish (...)inmediatamente después de intercambiar buffers. Esto borrará la cola de comandos, pero también significa que la GPU y la CPU se sincronizarán (lo que no es bueno si desea que la GPU funcione en todo momento).
Andon M. Coleman
1

¿Asumo que estás familiarizado con este experimento ?

Esencialmente, John Carmack estaba haciendo algo similar, grabando la pantalla y sincronizando los píxeles enviados a la pantalla. Descubrió que buena parte de la latencia provenía de la pantalla. Otros factores fueron el retraso de entrada del teclado, los controladores de video y, por supuesto, la ejecución del programa en sí.

MichaelHouse
fuente