Best way to draw many primitives

Hi all,

currently I work on an app that processes streaming video from a kinect. I wish to draw a lot of circles for a part of this (~20000). It all works fine, but drawing the circles drops the framerate quite a bit, from around 50 fps to 20 and further down the more ofCircle calls I make.

Currently the circles are drawn into an fbo which has a size of 1920x1440 (large size so lines are somewhat smoothed upon drawing, which only partly works the way I would wish for it to work, but I don’t know better ways for smoothing; anyhow I wanna have this resolution). The color and size of the circles is dependent of the depth value of pixels retrieved from the kinect.

I wonder which is the best way to proceed from here, ie. where best invest time, to get the framerate back to at least 30 fps, but preferably higher (also I plan on adding more effects in the furture). I thought of these options:

  • using a vbo to load vertices into (which makes me wonder how I would get the vbo to draw circles)
  • using vertex+fragment shaders (which would require me to study shaders)
  • using a geometry shader also to translate points into circles

Here is a video of what this looks like: http://youtu.be/BQ6_8nyQuDo (colored part starts at 2:00)
Any ideas appreciated!
menno

using an ofPath (or several, one per size for example) you can create the circles, then access them using path.getTessellation() which will give you an ofMesh that you can put in an ofVboMesh. Once you have the circles in the ofVboMesh you can use drawInstanced to draw lots of instances of each circle using a simple shader to position them

take a look at gl/vboMeshDrawInstancedExample/ to see how drawInstanced works

thanks @arturo, I will give that a try. While searching for info I also came across instancing as a solution for drawing many primitives. I wonder: will that approach still allow me to alter color and/or size of individual circles? (Or in case of squares, also rotation).

edit: looking at the vboMeshDrawInstancedExample I get the idea that it is quite possible to influence the appearance of single primitives; I will study it more closely to see how to control that from within oF.

On a side note: I had to edit out the check for “glDrawElementsInstanced” in main.cpp in order for the example to run (without USE_PROGRAMMABLE_GL). Then, since I am on Ubuntu and on a laptop with an NVIDIA cuda card with “Optimus”, I need to start the built app from the command line with “optirun” (using bumblebee). Running the app from within CodeBlocks doesn’t work cause it will use the onboard Intel graphics card with causes the shaders to fail from loading.

also a super fast way of drawing circles is to draw a mesh in points mode and set:

glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);

which will draw the points as circles. using an ofVboMesh just add a vertex for every circle you want to draw. you can even add a custom attribute through the internal vbo to set the the point size and use a simple shader to set the size of the points based on that attribute

with this method you can also add a color per circle by adding a color per vertex in the mesh, or even a texture or use a custom fragment shader to do something more advanced

1 Like

here’s a small example on how to draw any shape using custom attributes and billboards with the programmable renderer: just create a texture, dot.png with the shape you want to use. i’m getting 60fps with 50000 particles


//--------------------------------------------------------------
void ofApp::setup(){
    ofBackground(255);
    const int num_particles = 50000;
    mesh.setMode(OF_PRIMITIVE_POINTS);
    mesh.getVertices().resize(num_particles);
    mesh.getColors().resize(num_particles);
    pointSizes.resize(num_particles);
    shader.load("points");
    for(size_t i=0;i<mesh.getVertices().size();i++){
        mesh.getVertices()[i].x = ofRandom(0,ofGetWidth());
        mesh.getVertices()[i].y = ofRandom(0,ofGetHeight());
        mesh.getColors()[i].r = ofRandom(0,1);
        mesh.getColors()[i].g = ofRandom(0,1);
        mesh.getColors()[i].b = ofRandom(0,1);
        pointSizes[i] = ofRandom(2,15);
    }

    mesh.getVbo().setAttributeData(shader.getAttributeLocation("pointsize"), &pointSizes[0], 1, pointSizes.size(), GL_STATIC_DRAW);

    ofDisableArbTex();
    dot.loadImage("dot.png");
}

//--------------------------------------------------------------
void ofApp::draw(){
    shader.begin();
    dot.bind();
    mesh.draw();
    dot.unbind();
    shader.end();

    ofSetColor(0);
    ofDrawBitmapString(ofToString(ofGetFrameRate()),20,20);
}

and the shaders:

vertex:

#version 150

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 textureMatrix;
uniform mat4 modelViewProjectionMatrix;

in vec4  position;
in vec2  texcoord;
in vec4  color;
in vec3  normal;
in float pointsize;

out vec4 colorVarying;
out vec2 texCoordVarying;
out vec4 normalVarying;

void main()
{
    colorVarying = color;
    texCoordVarying = (textureMatrix*vec4(texcoord.x,texcoord.y,0,1)).xy;
    gl_Position = modelViewProjectionMatrix * position;
    gl_PointSize = pointsize;
}

fragment

#version 150
out vec4 fragColor;

uniform sampler2D src_tex_unit0;
uniform float usingTexture;
uniform float usingColors;
uniform vec4 globalColor;

in float depth;
in vec4 colorVarying;
in vec2 texCoordVarying;


void main(){
    fragColor = texture(src_tex_unit0, gl_PointCoord) * colorVarying;
}
1 Like

Hi @arturo, thanks a lot for the example. I got it to compile fine, also the shaders give no errors (using optirun on Ubuntu). However I get nothing on the screen when running the resulting app. Maybe this has to do with me using Ubuntu Studio on a laptop, with an NVIDIA geforce gt 540m, in conjunction with version 150 of GLSL. The BillboardsExample actually runs fine, and is quite similar, but doesn’t use in and out variables in the shaders…

Also I will still persue the method described in your previous reply some time later.

Thanks again, Menno.

Hey @arturo,
I’ve been studying your example, and a book about GLSL, but all I get is a whole lot of dots on top of each other in the middle of the window; it’s size changes as I change the code. Could this be due to my hardware? I tried changing to version 120, using the deprecated varying keyword, but the result remains the same.
The billboards example does work, I wonder where the difference is. I tried replacing the shaders with the ones from the billboards example, but then nothing gets drawn. Also I tried adding normals, but then also I see nothing.
Could it be somehow the coordinates don’t get to the shader in the right way? I’m really new to GLSL…
Thanks! Menno.

that’s all the example does :slight_smile: i was just trying to show how to draw lots of shapes efficiently but it does nothing more than that. you can use that code to do something similar to what you are doing in you example, instead of using random positions and colors set the ones you need and change them in update

ok :slight_smile: that’s clear then!
though from these lines of code I would expect the shapes to appear randomly in different places on the window, not just in the middle:

mesh.getVertices()[i].x = ofRandom(0,ofGetWidth());
mesh.getVertices()[i].y = ofRandom(0,ofGetHeight());

When I change that to say

mesh.getVertices()[i].x = 100;
mesh.getVertices()[i].y = 100;

The position of the one point I see doesn´t change, it´s still in the middle of the window. But I would expect the points to be in different possition as the mesh coordinates get sent to the vertex shader?

Aside from that, I was wondering what is the meaning off “billboard” in this context? Is it a normal term in graphics programming when applying a texture to a particle?

mmh, yeah they should appear randomly through the screen, you need to enable the programmable renderer, in main, before ofSetupOpenGL

ofSetCurrentRenderer(ofGLProgrammableRenderer::TYPE);

billboard is just a textured point so you get a texture which is always looking to the camera

Hmmm with the programmable renderer I run into trouble (which I had already noted with some examples):

$ primusrun ./testShaders_debug
[ error ] ofAppGLFWWindow: couldn't create GLFW window
Segmentation fault (core dumped)
$ _

This could very well be due to me using my NVIDIA “optimus enabled” graphics card under Ubuntu (studio 13.10, 64 bits). Running without bumblebee yields the same results.
glxinfo tell me (among listing many extensions):

OpenGL vendor string: NVIDIA Corporation
OpenGL renderer string: GeForce GT 540M/PCIe/SSE2
OpenGL version string: 4.2.0 NVIDIA 304.88
OpenGL shading language version string: 4.20 NVIDIA via Cg compiler

Apparently something goes wring with the call to setupOpenGL() after it has been told to use the programmable renderer. This remains so when I define USE_PROGRAMMABLE_GL. I tried pinpointing the reason/precise location, but don’t really know the structure of the framework, ie. what exactly happens inside and which calls are precisely made. I see this error message appear a number of times in the file opAppGLFWWindow.cpp…
Any ideas? Or would it be better to continue under MS Windows (I much prefer not to)?

I guess I could also stick to a deprecated version of OpenGL and GLSL, using predefined variables inside the shaders? Which makes me wonder what about makes it to be considered non-programmable… :slight_smile:

edit:

Commenting out these lines (164-165) from ofAppGLFWWindow,cpp allows the program to start using optirun/primusrun:

glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

Now the app runs fine, the shaders give no errors. The screen is filled with randomly colored dots in random places (actually only when loading a png that contains a white image). However sizes are all just 1 pixel… Maybe commenting out these lines disables some functionality? Also directly increasing point size by changing gl_PointSize in the vertex shader for example to 10 has no effect: just dots.
This solution found here. See also this page where it is suggested to use glewExperimental…

another edit after looking here:

Adding

glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);

to the setup function allows for point size to change. However, they are square points, and the texture seems not to be used…

So far I have found no way to use a texture that gets drawn over a resized point: only the upper left pixel of a texture is used, for drawing the complete resized dot. Maybe it is something that my current hardware setup disallows me from doing with the programmable pipeline; the billboardsExample does is it with the fixed renderer using GLSL #120 (I think) and that works.

Also, when the size of drawn points increases, framerate drops significantly: from 60 fps to about 20. That is in a app that draws a maximum of almost 50k dots (depends on the size of blobs in the screen, found using the depth image from a Kinect).
edit: this improves once I don’t draw the mesh inside a float fbo; framerate remains at least ~40 fps.

Finally, when drawing dots as squares (so not using glEnable(GL_POINT_SMOOTH)), their size doesn’t change so smoothly as it would when doing the same thing with ofRect(). I will try implementing the same thing using triangles to see what happens.

However, thanks a lot for the help, I do get progress!