# Why does this geometry shader slow down my program so much?

I have an OpenGL program, and I'm rendering a terrain mesh. I displace the vertices in the vertex buffer and don't really color them in the fragment shader yet. I'm adding a geometry shader one part at a time.

Before I added the geometry shader, when I was just programming the fragment and vertex shading steps of the pipeline, I was getting framerates of about 30+. Enough that I couldn't notice any choppiness. After adding the geometry shader, I get around 5 frames per second. Why? This is the entirety of the geometry shader:

#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();
}


Isn't this exactly what OpenGL was doing without the geometry shader?

Isn't this exactly what OpenGL was doing without the geometry shader?

No, it isn't. The GS is an optional step, not a step that has a default.

In order for OpenGL to execute a geometry shader, it must perform what is known as "primitive assembly". When you render a series of triangles via GL_TRIANGLE_STRIP, OpenGL will do internal stuff to convert every 3 adjacent vertices into an individual triangle, modifying the winding order appropriately.

Normally, when not using a GS, this process is performed once. When you use a GS however, it must be performed before the GS executes. But it must also be performed after the GS, because a GS can output a totally different primitive type (e.g. quads).

So now you're making the system basically do a bunch of extra work for nothing. After all, OpenGL can't assume that your GS is doing nothing (that's a undecidable problem).

Furthermore, a number of optimizations no longer function in the presence of a GS. Consider indexed rendering.

Each index from an element array buffer will produce the same outputs from a vertex shader. So the GPU will often cache these outputs in a post-T&L cache. If it sees an index that is already in the cache, the VS is not run again; it just fetches data from the cache.

What is "it"? "It" is... the primitive assembly unit. Yeah, that thing that gets run twice when you use a GS. The index caching stuff? It only works for the inputs of the GS.

So what happens to the outputs of the GS? Well, that's hardware dependent. But it has to go into some kind of memory buffer. And therein lies the problem: that buffer isn't indexed at all. It's like a glDrawArrays situation.

So if you send an index buffer of 0, 1, 2, 0, 2, 3, this would translate into 4 vertices in the post-T&L cache. But the post-GS buffer of vertices now has 6 vertices in it. The post-GS buffer uses more space. So if you go through the trouble of making properly post-T&L optimized triangle lists or strips, and you flip on a pass-through GS like yours, you basically killed about half of your performance gains from that optimization.

It wasn't useless, but it does hurt.

Adding to this is the fact that many GL 3.x-class GPUs (aka: DX10) had rather small post-GS buffers. The smaller the buffer, the fewer GS invocations you can have active simultaneously. So your hardware effectively bottlenecks on the GS. Because tessellation is a big feature of 4.x class hardware, most such hardware has buffers sufficient to make heavier GS use viable.

So using a GS is more likely to make your code vertex processing bottlenecked. Of course, you can always use that to your advantage by making your vertex and fragment shaders more complex, since it's just free performance at that point.

• Does the exception for point sprites generalize to shaders of layout(points) in;? Or is it the fixed output size? Or perhaps both? – Philip Guin Jan 18 '15 at 8:28