3

I'm writing a simple but useful OpenGL program for my work, which consists of showing how a vector field looks like. So the program simply takes the data from a file and draws arrows. I need to draw a few thousands of arrows. I'm using Qt for windows and OpenGL API.

The arrow unit is a cylinder and a cone, combined together in the function Arrow().

for(long i = 0; i < modifiedArrows.size(); i++) {
    glColor4d(modifiedArrows[i].color.redF(),modifiedArrows[i].color.greenF(),
        modifiedArrows[i].color.blueF(),modifiedArrows[i].opacity);
    openGLobj->Arrow(modifiedArrows[i].fromX,modifiedArrows[i].fromY,
        modifiedArrows[i].fromZ,modifiedArrows[i].toX,
        modifiedArrows[i].toY,modifiedArrows[i].toZ, 
        simulationSettings->vectorsThickness);
}

Now the problem is that running an infinite loop to keep drawing this gets the CPU fully busy, which is not so nice. I tried as much as possible to remove all calculations from the paintGL() functions, and only simple ones remained. I end The paintGL() function with glFlush() and glFinish(), and yet I always have the main CPU full.

If I remove this loop, the CPU doesn't get too busy anymore. But I have to draw thousands of arrows anyway.

Is there any solution to this other than parallelizing?

genpfault
  • 51,148
  • 11
  • 85
  • 139
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 3
    You mean "if I tell my CPU to draw something, and then when that is done, to draw something, and then when that is done, draw something and then....", then my CPU will do *nothing* except drawing something. Does this really surprise you? Your CPU does what you tell it to do, and you tell it to repeatedly draw. So it repeatedly draws. If you want the CPU to not-draw, then you have to add code to your loop which does something else. between iterations. Why is it a *problem* that your CPU is doing this? – jalf Feb 21 '13 at 16:22
  • Any chance you can create a draw list of some sort for the arrows and only modify them if the data changes? This way you only need to setup things occasionally and your CPU should be freed to do other things. I would also check and make sure that you are frame limiting your display - no need to update at 2000 FPS for something like this. You can sleep or do something else if your FPS goes above say 60 or so. – Michael Dorgan Feb 21 '13 at 16:22
  • Your loop isn't infinite - perhaps you're wrapping this in some calling code that loops without pauses? – Eamon Nerbonne Feb 21 '13 at 16:22
  • Well, the thing is that I can imagine that the calculations are simple and the sync rate is much faster than them, but apparently it's not the case. What do pro opengl programmers do in games for example? do they replot just when a change happens? And one more question, how can I set the FPS in my OpenGL program? – The Quantum Physicist Feb 21 '13 at 16:56
  • Ugh, immediate mode to draw thousands of identical objects. This is something instancing would work very nicely for. Of course it depends on what generation your graphics card is, if it's 12-14 years old, that's no go. – Damon Feb 21 '13 at 18:09

2 Answers2

6

You didn't pointed out on how you have implemented your openGLobj->Arrow method, but if you are using 100% CPU time on this, you are probably painting the arrows with immediate mode. This is really CPU intensive, because you have to transfer data from CPU to the GPU for every single instruction inside glBegin() and glEnd(). If you are using GLUT to draw your data, it's really ineficient too.

The way to go here is use GPU memory and processing power to dipslay your data. Phyatt has already pointed you some directions, but I will try to be more specific: use a Vertex Buffer Object (VBO).

The idea is to pre-allocate the needed memory for display your data on GPU and just update this chunk of memory when needed. This will probably make a huge difference on the efficience of your code, because you will use the efficient video card driver to handle the CPU->GPU transfers.

To illustrate the concept, I will show you just some pseudo-code in the end of the answer, but it's by no means completely correct. I didn't tested it and didn't had time to implement the drawing for you, but's it's a concept that can clarify your mind.

class Form
{
    public:
    Form()
    {
        // generate a new VBO and get the associated ID
        glGenBuffers(1, &vboId);

        // bind VBO in order to use
        glBindBuffer(GL_ARRAY_BUFFER, vboId);

        //Populate the buffer vertices.
        generateVertices();

        // upload data to VBO
        glBufferData(GL_ARRAY_BUFFER_ARB, vertices.size(), vertices.data(), GL_STATIC_DRAW_ARB);
    }

    ~Form()
    {
        // it is safe to delete after copying data to VBO
        delete [] vertices;

        // delete VBO when program terminated
        glDeleteBuffersARB(1, &vboId);
    }

    //Implementing as virtual, because if you reimplement it on the child class, it will call the child method :)
    //Generally you will not need to reimplement this class
    virtual void draw()
    {
        glBindBuffer(GL_ARRAY_BUFFER, vboId);
        glEnableClientState(GL_VERTEX_ARRAY);           
        glVertexPointer(3, GL_FLOAT, 0, 0);

        //I am drawing the form as triangles, maybe you want to do it in your own way. Do it as you need! :)
        //Look! I am not using glBegin() and glEnd(), I am letting the video card driver handle the CPU->GPU 
        //transfer in a single instruction!
        glDrawElements(GL_TRIANGLES, vertices.size(), GL_UNSIGNED_BYTE, 0);

        glDisableClientState(GL_VERTEX_ARRAY);

        // bind with 0, so, switch back to normal pointer operation
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
    }

private:
    //Populate the vertices vector with the form vertices.
    //Remember, any geometric form in OpenGL is rendered as primitives (points, quads, triangles, etc).
    //The common way of rendering this is to use multiple triangles.
    //You can draw it using glBegin() and glEnd() just to debug. After that, instead of rendering the triangles, just put
    //the generated vertices inside the vertices buffer.
    //Consider that it's at origin. You can use push's and pop's to apply transformations to the form.
    //Each form(cone or cilinder) will have its own way of drawing.
    virtual void generateVertices() = 0;

    GLuint vboId; 
    std::vector<GLfloat> vertices;
}

class Cone : public Form
{
public:
    Cone() : Form() {}
    ~Cone() : ~Form() {}

private:
    void generateVertices()
    {
        //Populate the vertices with cone's formula. Good exercise :)
        //Reference: http://mathworld.wolfram.com/Cone.html
    }

    GLuint vboId; 
    std::vector<GLfloat> vertices;
}

class Cilinder : public Form
{
public:
    Cone() : Form() {}
    ~Cone() : ~Form() {}

private:
    void generateVertices()
    {
        //Populate the vertices with cilinders's formula. Good exercise :)
        //Reference: http://math.about.com/od/formulas/ss/surfaceareavol_3.htm
    }

    GLuint vboId; 
    std::vector<GLfloat> vertices;
}


class Visualizer : public QOpenGLWidget
{
public:
    //Reimplement the draw function to draw each arrow for each data using the classes below.
    void updateGL()
    {
        for(uint i = 0; i<data.size(); i++)
        {
            //I really don't have a clue on how you position your arrows around your world model.
            //Keep in mind that those functions glPush, glPop and glMatrix are deprecated. I recommend you reading
            //http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Chapter-3:-3D-transformation-and-projection.html if you want to implement this in the most efficient way.
            glPush();
                glMatrix(data[i].transform());
                cilinder.draw();
                cone.draw();
            glPop();
        }
    }

private:
    Cone cone;
    Cilinder cilinder;
    std::vector<Data> data;
} 

As a final note, I can't assure you that this is the most efficient way of doing things. Probably, if you have a HUGE ammount of data, you would need some data-structure like Octrees or scene-graphs to optimize your code.

I recommend you taking a look at OpenSceneGraph or Visualization ToolKit to see if that methods are not already implemented for you, what would save you a lot of time.

Community
  • 1
  • 1
Ian Medeiros
  • 1,746
  • 9
  • 24
  • Thanks. I'll try to implement your method and let you know! – The Quantum Physicist Feb 22 '13 at 09:10
  • 1
    @SamerAfach Don't forget to look at the recomended libraries! OpenSceneGraph have some nice drawing methods! I have no experience with Visualization Toolkit, but its the library used by PCL to render their normals, witch is directly correlated to your problem! :) – Ian Medeiros Feb 22 '13 at 12:52
  • Actually I use gluCylinder(quadObj, D, D, L-4*D, 32, 1); to create cylinders. Can't I buffer quad objects rather than buffering vertices? – The Quantum Physicist Feb 23 '13 at 13:22
  • No. As far as I know, glu uses immediate mode to render their quadrics. With some reffined search, you probably can find some code that already generates ordered cilinder vertices. You can even look at gluCylinder code to implement it yourself. Have you looked at the recommended libraries? – Ian Medeiros Feb 23 '13 at 15:21
  • I checked them. They look complicated and required really a long time to change my program to fit to them. I would like to stay with raw OpenGL. Thanks for the advice, I'll keep trying :) – The Quantum Physicist Feb 23 '13 at 17:39
  • One little question. Do you update data every frame? Because if the data is constant, creating a bigger vertex buffer for all arrows would be even better. – Ian Medeiros Feb 23 '13 at 21:11
  • Data is constant actually, and I think buffering all arrows would be a great idea, but I don't even know how to do it... Another nice example about that would be nice :) – The Quantum Physicist Feb 23 '13 at 21:59
1

Try this link for some ideas:

Basically what I've seen that people do to increase their FPS and drop quality includes the following:

  • Using DisplayLists. (cache complex or repetitive matrix stacks).

  • Using Vertex Arrays.

  • Using simpler geometry with fewer faces.

  • Using simpler lighting.

  • Using simpler textures.

The main advantage of OpenGL is that is works with a lot of graphics cards, which are built to do a lot of the 4x4 matrix transformations, multiplications, etc, very quickly and they provide more RAM memory for storing rendered or partially rendered objects.

Assuming that all the vectors are changing so much and often that you can't cache any of the renderings...

My approach to this problem would be to simplify the drawing down to just lines and points, and get that to draw at the desired frame rate. (A line for your cylinder and a colored point on the end for the direction.)

After that draws fast enough, try making the drawing more complex, like a rectangular prism instead of a line, and a pyramid instead of a colored point.

Rounded objects typically require a lot more surfaces and calculations.

I am not an expert on the subject, but I would google other OpenGL tutorials that deal with optimization.

Hope that helps.

EDIT: Removed references to NeHe tutorials because of comments.

Community
  • 1
  • 1
phyatt
  • 18,472
  • 5
  • 61
  • 80
  • Also, he should consider using vertex arrays. – Eamon Nerbonne Feb 21 '13 at 17:20
  • Oh yeah, I forgot about those. I'll put those in. – phyatt Feb 21 '13 at 17:21
  • And typically openGl, there's yet another handy way... vertex buffers: http://www.songho.ca/opengl/gl_vbo.html Never used those, but sounds promising, right? – Eamon Nerbonne Feb 21 '13 at 17:25
  • @NicolBolas What OpenGL reference do you recommend? – phyatt Feb 21 '13 at 19:47
  • @NicolBolas, Just curious, what is wrong with NeHe? Is it outdated? Are the methods wrong? Would you rather that StackOverflow contain all the answers? – Anish Ramaswamy Oct 15 '13 at 07:49
  • Asking google, it looks like the consensus on one forum, is that the only good thing NeHe has, is examples in lots of languages and platforms. Besides that (people say) its outdated, uses poor programming practices, and shows one "how", that isn't necessarily the best way, and as you get deeper into OpenGL, you end up needing to unlearn the stuff you picked up on NeHe. I guess the hardcore OpenGL people out there learned from the Redbook or from the docs, or other OOP oriented or optimization minded tutorials/code samples. – phyatt Oct 16 '13 at 07:36
  • Personally I didn't like the use of GLU or glut throughout the tutorials. Any other graphics library is probably a good substitute. I used Qt while going through some of my first tutorials. – phyatt Oct 16 '13 at 07:38