Full Screen Triangle into FilterManager source?

Hi again!

I’m right now dissecting the FilterManager and swapped out the CardMaker
for a custom Full Screen Triangle using three empty vertices. The Triangle itself
is created in the vertex shader without actually pulling any data from memory.

There are a few sites going into this, like this one:
rauwendaal.net/2014/06/14/rende … in-opengl/

And before you wonder, it really is faster than doing a quad, for several reasons.

Not only does it save a vertex and the shader from pulling data from memory,
it also saves processing time on the adjacent edges of the two triangles making up a quad.

Due to pixels being processed in groups, the touching lines of the triangle partly get processed twice,
which can be a performance hog for heavy shaders. The Triangle eliminates this.

For my rather moderate shader doing six passes (aka 6 Triangles) it saves me 0.3-0.4msec rendering time.

I think that’s quite the improvement for such a simple change.

Now, I wanted to ask if you would want me to put that into the FilterManager and create a Pull request on github. I only ever did this ONCE (a pull request, so I’m new to “open source-ing”), so I thought about going the safe route adding a “useTriangle” boolean defaulted to False, so the new code wouldn’t possibly make old code potentially unusable.

So it would then look like this, in FilterManager:

def __init__(self, win, cam, forcex=0, forcey=0, UseTriangle=False):

And the following code would then check if it’s True or False, choosing between Quad or Triangle.

What do you think?

I was thinking the full-screen-triangle is one of those urban myths, but if you say you got 0.3-0.4msec for free, then yeah, I for one want it!

Can you post the code? I got a setup with ~8 intermediate stages and I’d like to test if I also get the same performance boost.

It’s like an open secret “everyone” knows about and rarely talks about, which leads to this paradoxical situation where the vast majority thinks it’s just a myth. lol

According to people on StackOverflow, even professionals from nvidia and amd suggest using it.

Now, I’m not 100% certain that this is the best way to set up three empty vertices, but it works.

I’ve used the shader from the site I linked above. Not sure the texture coordinates work,
I got issues with them with my ray marcher, but that could totally be me.
I use FragCoord for texelFetches, which works just fine.

# cm = CardMaker("filter-base-quad")
        # cm.setFrameFullscreenQuad()
        # quad = NodePath(cm.generate())
        # quad = self.Triangle;#NodePath(self.Triangle);
        Vertices=GeomVertexData('Triangle', GeomVertexFormat.getV3(), Geom.UHStatic)
        Vertex=GeomVertexWriter(Vertices, 'vertex')
        Vertex.addData3d(0.0,0.0,0.0)
        Vertex.addData3d(0.0,0.0,0.0)
        Vertex.addData3d(0.0,0.0,0.0)
        Triangle = GeomTriangles(Geom.UHStatic)
        Triangle.addVertices(0,1,2)
        Triangle.closePrimitive()
        Primitive=Geom(Vertices)
        Primitive.addPrimitive(Triangle)
        gNode=GeomNode('FullScreenTriangle')
        gNode.addGeom(Primitive)

        quad = NodePath(gNode)
        quad.setDepthTest(0)
        quad.setDepthWrite(0)
        quad.setTexture(colortex)
        quad.setColor(1, 0.5, 0.5, 1)

The above code belongs into both renderSceneInto and renderQuadInto. In both functions there’s the cm.generate() which you can see above.

The vertex shader:

#version 130
 
out vec2 texCoord;
 
void main()
{
    float x = -1.0 + float((gl_VertexID & 1) << 2);
    float y = -1.0 + float((gl_VertexID & 2) << 1);
    texCoord.x = (x+1.0)*0.5;
    texCoord.y = (y+1.0)*0.5;
    gl_Position = vec4(x, y, 0, 1);
}

That’s it!

Please post your results!

It looks like I’m only getting black in the buffers, I’ll try again later with FragCoord for uvs and/or a simpler setup.

I’m not very good at sharing code. It worked for me right out of the box.
You might want to try the gl-coordinates config line.

Not on my machine right now, else I’d copy it here.

Not sure it makes a difference, but i too uncertain to rule it out.

Sure, I’d be happy to entertain a patch for the FilterManager. The RenderPipeline’s render target system also uses the oversized triangle; a neat little trick. I see little value in configuring it via the constructor, though; perhaps a configuration variable might be more appropriate.

Note that in the master branch of Panda, you don’t need to write dummy vertices into the GeomVertexData to do attribute-less rendering. This should suffice:

Vertices = GeomVertexData('Triangle', GeomVertexFormat.getEmpty(), GeomEnums.UHStatic)

By setting it to the “empty” format, Panda will know that you are trying to do attribute-less rendering and won’t check that the number of rows matches the referenced indices.

Even before 1.10, you don’t need to have a GeomVertexWriter, just calling Vertices.setNumRows(3) should zero-initialise three vertices. It’s also not necessary to ever call closePrimitive() on a GeomTriangles.

This is a more elaborate sample program showing attribute-less rendering in OpenGL:
gist.github.com/rdb/e8c0d048285 … cb6ab4284c

By the way, I’d be interested to see the performance difference between using your bitshifting trick and just indexing into a const vec2[3] array declared in the shader. I imagine binary operations might be rather expensive in a shader.

Thank you for the example, rdb, I will look into it!
I can’t take credit for the bit shifting, btw, and will try constants as soon as I’ve sorted my current issue. :slight_smile:

I don’t understand your suggested approach.
It seems to me that this way everyone involved has to write more code than necessary.

I assume you mean that after the creation of the FilterManager object, one should call another function configuring it as Triangle, instead of Quad. I don’t see how this is a better approach than transparently adding it in the constructor and adding a variable in “self”.

With your approach not only I would have to add a function (or modify an existing one I don’t know of) AND the programmer who wants to use it has to add it that function call as well. Plus, anyone who wants to “port” from Quad to Triangle would have to add it in as well, instead of simply adding a True at the end of the object’s construction.

Could you elaborate on why you would do it differently?

No? I’m not suggesting adding a function at all. I’m suggesting reading a configuration variable from Config.prc that specifies whether Panda uses a triangle or quad. It’s one line of code to fetch such a value from the configuration manager.

… Okay!

But still, why such an approach? It feels completely misplaced. Making it a configuration variable will just hide it from most people who aren’t aware of most of these variables. It also makes it look like that the triangle should be just an option, while it actually should be default and still adds more work for me, compared to the few lines it took me to do already.

Even if the triangle was default, no matter what, I’d have to look into how the configuration file works and everything related (as I don’t know any of that) while my current solution is extremely elegant, completely transparent and simple.

Thank you for the suggestion, but I guess then I have to pass. I mean … in the end it’s mostly your code AFAIK. You might not be happy with what I write and then feel the need to rewrite it. I’m not happy with the FilterManager as a whole and making it even worse does not seem to be a way to move forward. No offense intended and I’m sorry if I came across the wrong way in any of my posts.

Yeah, my original assumption was that it was going to be the default. The reason I suggested a config var was that it seemed like an implementation detail that people should only be encouraged to change if they are experiencing issues. Giving it a bit more thought, however, this is a bit silly; it does require people to write their vertex shaders differently, so this is definitely something that should be explicitly specified.

With that in mind, it seems more appropriate to have it as parameter like you suggested. However, it might be even a bit more elegant to place it in the renderQuadInto and renderSceneInto calls, since different passes can use different shaders and each shader may assume a particular geometry.

I’m a little surprised that you feel so strongly about such a trivial thing, and I’m certainly not trying to push my own design standards or dissuade you from contributing. Either way, FilterManager is not my code, and I also think there are major flaws in its design. I intend to see it replaced altogether by a new system, one that would certainly make use of an oversized triangle by default, and one that provided a default vertex shader so that the developer wouldn’t have to write their shaders differently depending on what geometry is being used.