opengl: glFlush () frente a glFinish ()

105

Tengo problemas para distinguir la diferencia práctica entre llamar glFlush()y glFinish().

Los documentos dicen que glFlush()y glFinish()empujarán todas las operaciones almacenadas en búfer a OpenGL para que uno pueda estar seguro de que todas se ejecutarán, la diferencia es que glFlush()regresa inmediatamente donde como glFinish()bloques hasta que se completan todas las operaciones.

Después de leer las definiciones, pensé que si usaba glFlush()eso probablemente me encontraría con el problema de enviar más operaciones a OpenGL de las que podría ejecutar. Entonces, solo para probar, cambié mi glFinish()por a glFlush()y he aquí, mi programa se ejecutó (por lo que pude ver), exactamente lo mismo; velocidades de cuadro, uso de recursos, todo era igual.

Entonces me pregunto si hay mucha diferencia entre las dos llamadas, o si mi código hace que se ejecuten de la misma manera. O dónde debería usarse uno frente al otro. También pensé que OpenGL tendría alguna llamada glIsDone()para verificar si todos los comandos almacenados en búfer para a glFlush()están completos o no (por lo que uno no envía operaciones a OpenGL más rápido de lo que se pueden ejecutar), pero no pude encontrar tal función .

Mi código es el típico bucle de juego:

while (running) {
    process_stuff();
    render_stuff();
}
jay.lee
fuente

Respuestas:

93

Tenga en cuenta que estos comandos existen desde los primeros días de OpenGL. glFlush asegura que los comandos OpenGL anteriores deben completarse en un tiempo finito ( especificaciones de OpenGL 2.1 , página 245). Si dibuja directamente en el búfer frontal, esto garantizará que los controladores OpenGL comiencen a dibujar sin demasiado retraso. Podrías pensar en una escena compleja que aparece objeto tras objeto en la pantalla, cuando llamas a glFlush después de cada objeto. Sin embargo, cuando se usa doble búfer, glFlush prácticamente no tiene ningún efecto, ya que los cambios no serán visibles hasta que intercambie los búferes.

glFinish no regresa hasta que todos los efectos de los comandos emitidos [...] previamente se hayan realizado completamente . Esto significa que la ejecución de su programa espera aquí hasta que se dibuja hasta el último píxel y OpenGL no tiene nada más que hacer. Si renderiza directamente en el búfer frontal, glFinish es la llamada a realizar antes de usar las llamadas al sistema operativo para tomar capturas de pantalla. Es mucho menos útil para el almacenamiento en búfer doble, porque no ve los cambios que forzó a completar.

Entonces, si usa doble búfer, probablemente no necesitará ni glFlush ni glFinish. SwapBuffers dirige implícitamente las llamadas de OpenGL al búfer correcto, no es necesario llamar a glFlush primero . Y no le importe hacer hincapié en el controlador OpenGL: glFlush no se ahogará con demasiados comandos. No se garantiza que esta llamada regrese inmediatamente (lo que sea que eso signifique), por lo que puede tomar el tiempo necesario para procesar sus comandos.

Malte Clasen
fuente
1
¿Qué quiere decir con que glFlush "debe completarse en un tiempo FINITO" y luego decir que glFlush no se ahogará porque "puede tomar CUALQUIER tiempo que necesite procesar sus comandos"? (Gorras mías) ¿No son mutuamente excluyentes?
Praxiteles
1
Siempre que termine en algún momento, cumple con los criterios de tiempo FINITO. Algunos conductores no operan esta llamada por completo.
chrisvarnz
SwapBuffers es específico del contexto y solo necesita vaciar el contexto del hilo de llamada. Si se renderizan varios contextos, cada uno debe vaciarse manualmente, ya que no está garantizado que suceda al intercambiar búferes a través de otro contexto.
Andreas
22

Como han insinuado las otras respuestas, realmente no hay una buena respuesta según las especificaciones. La intención general glFlush()es que después de llamarlo, la CPU del host no tenga que hacer ningún trabajo relacionado con OpenGL: los comandos se habrán enviado al hardware de gráficos. La intención general de glFinish()es que después de que regrese, no quede trabajo restante, y los resultados deberían estar disponibles también en todas las API que no sean OpenGL (por ejemplo, lecturas del framebuffer, capturas de pantalla, etc.). Si eso es realmente lo que sucede depende del conductor. La especificación permite un montón de libertad en cuanto a lo que es legal.

Andy Ross
fuente
18

Siempre estuve confundido acerca de esos dos comandos también, pero esta imagen me lo dejó todo claro: Flush vs Finish aparentemente, algunos controladores de GPU no envían los comandos emitidos al hardware a menos que se haya acumulado una cierta cantidad de comandos. En este ejemplo, ese número es 5 .
La imagen muestra varios comandos OpenGL (A, B, C, D, E ...) que se han emitido. Como podemos ver en la parte superior, los comandos aún no se emiten porque la cola aún no está llena.

En el medio vemos cómo glFlush()afectan los comandos en cola. Le dice al controlador que envíe todos los comandos en cola al hardware (incluso si la cola aún no está llena). Esto no bloquea el hilo de llamada. Simplemente indica al controlador que es posible que no estemos enviando comandos adicionales. Por lo tanto, esperar a que se llene la cola sería una pérdida de tiempo.

En la parte inferior vemos un ejemplo usando glFinish(). Casi hace lo mismo que glFlush(), excepto que hace que el hilo de llamada espere hasta que el hardware haya procesado todos los comandos.

Imagen extraída del libro "Programación avanzada de gráficos con OpenGL".

Tara
fuente
De hecho, sé qué glFlushy glFinishhago, y no puedo decir lo que dice esa imagen. ¿Qué hay del lado izquierdo y del derecho? Además, ¿esa imagen fue publicada en el dominio público o bajo alguna licencia que le permite publicarla en Internet?
Nicol Bolas
Debería haberlo elaborado un poco. Acerca de la licencia: en realidad no estoy muy seguro. Me encontré con el PDF en Google mientras investigaba.
Tara
7

Si no vio ninguna diferencia de rendimiento, significa que está haciendo algo mal. Como algunos otros mencionaron, tampoco necesita llamar, pero si llama a glFinish, automáticamente está perdiendo el paralelismo que la GPU y la CPU pueden lograr. Déjame sumergirme más profundo:

En la práctica, todo el trabajo que envía al controlador se agrupa y se envía al hardware potencialmente mucho más tarde (por ejemplo, en el momento de SwapBuffer).

Por lo tanto, si está llamando a glFinish, esencialmente está forzando al controlador a enviar los comandos a la GPU (que había almacenado por lotes hasta entonces, y nunca le pidió a la GPU que trabajara) y paralizar la CPU hasta que los comandos presionados estén completamente ejecutado. Entonces, durante todo el tiempo que la GPU funciona, la CPU no (al menos en este hilo). Y todo el tiempo que la CPU hace su trabajo (principalmente comandos de procesamiento por lotes), la GPU no hace nada. Así que sí, glFinish debería perjudicar tu rendimiento. (Esta es una aproximación, ya que los controladores pueden comenzar a hacer que la GPU funcione en algunos comandos si muchos de ellos ya estaban en lotes. Sin embargo, no es típico, ya que los búferes de comandos tienden a ser lo suficientemente grandes como para contener muchos comandos).

Ahora, ¿por qué llamarías glFinish entonces? Las únicas veces que lo usé fueron cuando tuve errores de controlador. De hecho, si uno de los comandos que envía al hardware bloquea la GPU, la opción más sencilla para identificar qué comando es el culpable es llamar a glFinish después de cada Draw. De esa manera, puede reducir qué desencadena exactamente el bloqueo

Como nota al margen, las API como Direct3D no son compatibles con el concepto Finish en absoluto.

Bahbar
fuente
5
En "Ahora, ¿por qué llamarías glFinish entonces?". Si está cargando recursos (por ejemplo: texturas) en un hilo y usándolos para renderizar en otro, compartiéndolos a través de wglShareLists o similares, necesita llamar a glFinish antes de indicarle al hilo de renderizado que es libre de renderizar lo que se ha cargado de forma asincrónica.
Marco Mp
Como dije a continuación, esto parece una solución alternativa para un error de controlador. No puedo encontrar ninguna mención específica de este requisito. En Windows, el controlador debe tener un bloqueo, en Mac, debe hacer CGLLockContext. Pero usar glFinish parece muy incorrecto. Para saber cuando una textura es válida tienes que hacer tu propio bloqueo o evento en cualquier caso.
starmole
1
opencl opengl interop docs recomienda glFinish para la sincronización "Antes de llamar a clEnqueueAcquireGLObjects, la aplicación debe asegurarse de que se hayan completado todas las operaciones GL pendientes que acceden a los objetos especificados en mem_objects. Contextos GL con referencias pendientes a estos objetos "
Mark Essel
1
no "necesita" llamar a glFinish, puede usar objetos de sincronización.
chrisvarnz
Solo para agregar, desde la respuesta original: algunas implementaciones GL comenzaron a usar flush / finish para sincronizar entre contextos compartidos en diferentes hilos: developer.apple.com/library/ios/documentation/3DDrawing/… - Más notablemente Apple IOS. Creo que es fundamentalmente otro mal uso de la API. Pero esta es una advertencia a mi respuesta original: en algunas implementaciones, se necesita terminar / enjuagar. Pero el cómo y el por qué se define la implementación, no las especificaciones de OpenGL.
starmole
7

glFlush realmente se remonta a un modelo de servidor cliente. Envía todos los comandos gl a través de una tubería a un servidor gl. Esa tubería podría amortiguar. Al igual que cualquier archivo o E / S de red podría almacenar en búfer. glFlush solo dice "envíe el búfer ahora, incluso si aún no está lleno". En un sistema local, esto casi nunca es necesario porque es poco probable que una API OpenGL local se almacene en búfer y solo emite comandos directamente. Además, todos los comandos que provocan un renderizado real realizarán una descarga implícita.

glFinish, por otro lado, se hizo para medir el rendimiento. Una especie de PING al servidor GL. Envía un comando de ida y vuelta y espera hasta que el servidor responde "Estoy inactivo".

Sin embargo, hoy en día los conductores locales modernos tienen ideas bastante creativas sobre lo que significa estar inactivo. ¿Es "todos los píxeles están dibujados" o "mi cola de comandos tiene espacio"? También debido a que muchos programas antiguos esparcían glFlush y glFinish a lo largo de su código sin ninguna razón como codificación vudú, muchos controladores modernos simplemente los ignoran como una "optimización". No puedo culparlos por eso, de verdad.

En resumen: trate tanto glFinish como glFlush como sin operaciones en la práctica, a menos que esté codificando para un antiguo servidor SGI OpenGL remoto.

starmole
fuente
4
Incorrecto. Como en el comentario que dejé en la respuesta anterior: si está cargando recursos (por ejemplo: texturas) en un hilo y usándolos para renderizar en otro, con compartir a través de wglShareLists o similares, debe llamar a glFinish antes de señalar al hilo de renderizado que es libre de renderizar lo que se ha cargado de forma asincrónica. Y no es una operación no operativa como dijiste.
Marco Mp
De Verdad? ¿Puede respaldar la necesidad de llamar a glFinish antes de usar un objeto compartido con algún enlace de especificación? Puedo ver un controlador con errores que necesita esto. Y eso vuelve a lo que dije. La gente usa glFinish para evitar conductores con errores. Por eso, los controladores que funcionan correctamente lo ignoran por rendimiento. El caso de uso de la especificación original de creación de perfiles (y renderizado en búfer único) ya no funciona.
starmole
2
Experiencia personal de tener que lidiar con texturas corruptas, más esto: upperorderfun.com/blog/2011/05/26/… Si lo piensas, tiene sentido, sin embargo, ya que no es muy diferente de tener mutex u otras sincronizaciones. api entre subprocesos: antes de que un contexto pueda usar un recurso compartido, el otro tiene que completar la carga de ese recurso, por lo que es necesario asegurarse de que el búfer se haya vaciado. Pienso más en el caso de esquina de las especificaciones que en el error, pero no tengo pruebas de esta declaración :)
Marco Mp
Excelente respuesta, gracias. Especialmente "muchos programas antiguos esparcían glFlush y glFinish a lo largo de su código sin razón como vudú".
akauppi
¿Realmente no hay operaciones? ¿No son glFinish y / o glFlush muy valiosos para el procesamiento de imágenes de alta intensidad o el coprocesamiento matemático basado en GPU? Por ejemplo, no leemos los resultados del cálculo de la GPU desde el búfer de píxeles a la CPU hasta después de llamar a glFinish para asegurarnos de que se hayan escrito todos los cálculos de píxeles. ¿Nos estamos perdiendo algo?
Praxiteles
5

Echa un vistazo aquí . En resumen, dice:

glFinish () tiene el mismo efecto que glFlush (), con la adición de que glFinish () bloqueará hasta que se hayan ejecutado todos los comandos enviados.

Otro artículo describe otras diferencias:

  • Las funciones de intercambio (utilizadas en aplicaciones de doble búfer) eliminan automáticamente los comandos, por lo que no es necesario llamar glFlush
  • glFinish obliga a OpenGL a realizar comandos sobresalientes, lo cual es una mala idea (por ejemplo, con VSync)

En resumen, esto significa que ni siquiera necesita estas funciones cuando usa doble búfer, excepto si su implementación de swap-búfer no vacía automáticamente los comandos.

AndiDog
fuente
Sí, claro, pero el documento dice que ambos tienen el mismo efecto con pocas diferencias con respecto al sistema de ventanas, por ejemplo. Pero de todos modos
edité
Buen comentario sobre el matiz. Aclara si es necesario llamar glFlush () y LUEGO glFinish () - (una pregunta que teníamos después de leer todo lo anterior)
Praxiteles
4

No parece haber una forma de consultar el estado del búfer. Existe esta extensión de Apple que podría tener el mismo propósito, pero no parece multiplataforma (no la he probado). A simple vista, parece que antes de flushpresionar el comando de valla; luego puede consultar el estado de esa valla a medida que se mueve a través del búfer.

Me pregunto si podría usar flushlos comandos antes de almacenar en búfer, pero antes de comenzar a renderizar el siguiente cuadro al que llama finish. Esto le permitiría comenzar a procesar el siguiente cuadro a medida que funciona la GPU, pero si no lo hace para cuando regrese, finishse bloqueará para asegurarse de que todo esté en un estado nuevo.

No he probado esto, pero lo haré en breve.

Lo he probado en una aplicación antigua que tiene un uso bastante uniforme de CPU y GPU. (Originalmente se usó finish).

Cuando lo cambié a flushal final y finishal comienzo, no hubo problemas inmediatos. (¡Todo se veía bien!) La capacidad de respuesta del programa aumentó, probablemente porque la CPU no se detuvo esperando en la GPU. Definitivamente un método mejor.

A modo de comparación, lo quité finisheddel inicio del cuadro , me fui flush, y funcionó igual.

Entonces, diría use flushy finish, porque cuando el búfer está vacío en la llamada a finish, no hay impacto en el rendimiento. Y supongo que si el búfer estuviera lleno, debería querer hacerlo de finishtodos modos.

GManNickG
fuente
0

La pregunta es: ¿desea que su código continúe ejecutándose mientras se ejecutan los comandos de OpenGL, o solo que se ejecute después de que se hayan ejecutado sus comandos de OpenGL?

Esto puede ser importante en casos, como retrasos en la red, para tener cierta salida de consola solo después de que se hayan dibujado las imágenes o algo así.

luego
fuente