¿Por qué este sombreador de geometría ralentiza tanto mi programa?

27

Tengo un programa OpenGL y estoy renderizando una malla de terreno. Desplazo los vértices en el búfer de vértices y todavía no los coloreo en el sombreador de fragmentos. Estoy agregando un sombreador de geometría una parte a la vez.

Antes de agregar el sombreador de geometría, cuando solo estaba programando los pasos de sombreado de vértices y fragmentos de la tubería, obtenía tasas de fotogramas de aproximadamente 30+. Lo suficiente como para no notar ningún picado. Después de agregar el sombreador de geometría, obtengo alrededor de 5 cuadros por segundo. ¿Por qué? Esta es la totalidad del sombreador de geometría:

#version 420

layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;

void main()
{
    for (int i = 0; i < gl_in.length(); i++)
    {
        gl_Position = gl_in[i].gl_Position;
        EmitVertex();
    }
    EndPrimitive();
}

¿No es esto exactamente lo que estaba haciendo OpenGL sin el sombreador de geometría?

Avi
fuente

Respuestas:

40

¿No es esto exactamente lo que estaba haciendo OpenGL sin el sombreador de geometría?

No lo es. El GS es un paso opcional , no un paso que tiene un valor predeterminado.

Para que OpenGL ejecute un sombreador de geometría , debe realizar lo que se conoce como " ensamblaje primitivo ". Cuando renderiza una serie de triángulos a través de GL_TRIANGLE_STRIP, OpenGL hará cosas internas para convertir cada 3 vértices adyacentes en un triángulo individual, modificando el orden de bobinado adecuadamente.

Normalmente, cuando no se utiliza un GS, este proceso se realiza una vez. Sin embargo, cuando utiliza un GS, debe realizarse antes de que se ejecute el GS. Pero también debe realizarse después del GS, porque un GS puede generar un tipo primitivo totalmente diferente (por ejemplo, quads).

Así que ahora estás haciendo que el sistema básicamente haga un montón de trabajo extra por nada. Después de todo, OpenGL no puede asumir que su GS no está haciendo nada (ese es un problema indecidible).

Además, varias optimizaciones ya no funcionan en presencia de un GS. Considere la representación indexada.

Cada índice de un búfer de matriz de elementos producirá las mismas salidas de un sombreador de vértices. Por lo tanto, la GPU a menudo almacenará en caché estas salidas en un caché posterior a T&L . Si ve un índice que ya está en la caché, el VS no se ejecuta nuevamente; solo obtiene datos del caché.

Qué es"? "Es" es ... la unidad de ensamblaje primitiva . Sí, esa cosa que se ejecuta dos veces cuando usas un GS. El índice de almacenamiento en caché? Solo funciona para las entradas de GS.

Entonces, ¿qué pasa con las salidas de la GS? Bueno, eso depende del hardware. Pero tiene que ir a algún tipo de memoria intermedia. Y ahí radica el problema: ese búfer no está indexado en absoluto. Es como una situación de glDrawArrays.

Entonces, si envía un búfer de índice de 0, 1, 2, 0, 2, 3, esto se traduciría en 4 vértices en el caché posterior a T&L. Pero el búfer de vértices posterior a GS ahora tiene 6 vértices. El búfer posterior a GS usa más espacio. Entonces, si pasa por la molestia de hacer correctamente las listas o tiras de triángulos optimizadas después de T&L, y se voltea en un GS de transferencia como el suyo, básicamente eliminó aproximadamente la mitad de sus ganancias de rendimiento de esa optimización.

No fue inútil, pero duele.

A esto se agrega el hecho de que muchas GPU de clase GL 3.x (también conocidas como: DX10) tenían buffers posteriores a GS bastante pequeños. Cuanto más pequeño es el búfer, menos invocaciones de GS puede tener activas simultáneamente. Por lo tanto, su hardware efectivamente cuellos de botella en el GS. Debido a que la teselación es una gran característica del hardware de clase 4.x, la mayoría de dichos hardware tienen amortiguadores suficientes para hacer viable el uso más pesado de GS.

Por lo tanto, es más probable que usar un GS genere un cuello de botella en el procesamiento de vértices de su código. Por supuesto, siempre puede usar eso a su favor al hacer que sus sombreadores de vértices y fragmentos sean más complejos, ya que es solo un rendimiento gratuito en ese punto.

Para obtener más información sobre las ralentizaciones inducidas por GS, lea este artículo .

Aquí hay una regla básica sobre los GS: nunca use un GS porque cree que hará que el renderizado sea más rápido . Debe usarlo cuando haga posible lo que está tratando de hacer . Si lo que intenta hacer es una optimización, use otra cosa.

Las excepciones generales a esto son:

Nicol Bolas
fuente
Estoy tratando de calcular la inclinación de cada polígono tomando su altura más alta y restando su altura más baja. Sin embargo, si un sombreador de geometría necesariamente me ralentizará en esta cantidad, creo que podría hacerlo creativamente en el sombreador de vértices.
Avi
1
@Avi tenga en cuenta que los puntos más altos y más bajos en un triángulo no le darán su inclinación; Necesitas los tres puntos.
sam hocevar
2
Personalmente, siempre he encontrado que las instancias son más útiles para sprites puntuales que un GS.
Maximus Minimus
1
¿La excepción para puntos sprites generaliza a sombreadores de layout(points) in;? ¿O es el tamaño de salida fijo? O tal vez ambos?
Philip