使用 OpenGL 绘图而不杀死 CPU 并且不并行化

Posted

技术标签:

【中文标题】使用 OpenGL 绘图而不杀死 CPU 并且不并行化【英文标题】:Drawing with OpenGL without killing the CPU and without parallelizing 【发布时间】:2013-02-21 16:18:15 【问题描述】:

我正在为我的工作编写一个简单但有用的 OpenGL 程序,其中包括显示矢量场的样子。所以程序只是从文件中获取数据并绘制箭头。我需要画几千支箭。我正在使用 Qt for windows 和 OpenGL API。

箭头单位是一个圆柱体和一个圆锥体,在函数 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);

现在的问题是,运行一个无限循环来继续绘制它会使 CPU 完全忙碌,这不是很好。我尽可能地尝试从 paintGL() 函数中删除所有计算,只剩下简单的计算。我用 glFlush() 和 glFinish() 结束了 paintGL() 函数,但我的主 CPU 总是满的。

如果我删除这个循环,CPU 就不会变得太忙了。但无论如何我都要画上千支箭。

除了并行化之外,还有其他解决方案吗?

【问题讨论】:

你的意思是“如果我告诉我的 CPU 画一些东西,然后当它完成后,再画一些东西,然后当它完成后,画一些东西然后......”,那么我的CPU 将什么都不做 除了绘制一些东西。这真的让你感到惊讶吗?你的 CPU 做你告诉它做的事情,你告诉它重复绘制。所以它反复绘制。如果您希望 CPU 不绘制,那么您必须将代码添加到执行其他操作的循环中。迭代之间。为什么你的 CPU 这样做是一个问题 您是否有机会为箭头创建某种类型的绘制列表,并且仅在数据更改时才修改它们?这样你只需要偶尔设置一些东西,你的 CPU 应该可以腾出来做其他事情。我还会检查并确保您限制了显示器的帧数 - 不需要以 2000 FPS 的速度进行更新。如果您的 FPS 超过 60 左右,您可以睡觉或做其他事情。 你的循环不是无限的——也许你把它包装在一些没有暂停循环的调用代码中? 嗯,事情是我可以想象计算很简单,同步率比它们快得多,但显然事实并非如此。例如,专业的 opengl 程序员在游戏中做什么?他们只是在发生变化时重新绘制吗?还有一个问题,如何在我的 OpenGL 程序中设置 FPS? 呃,立即模式可以绘制数千个相同的对象。这是实例化可以很好地工作的东西。当然要看你的显卡是几代的了,12-14岁就不行了。 【参考方案1】:

你没有指出你是如何实现你的 openGLobj->Arrow 方法的,但是如果你在这方面使用了 100% 的 CPU 时间,你可能正在用immediate mode 绘制箭头。这确实是 CPU 密集型的,因为您必须为 glBegin() 和 glEnd() 中的每条指令将数据从 CPU 传输到 GPU。如果你使用 GLUT 来绘制你的数据,那也确实是低效的。

这里的方法是使用 GPU 内存和处理能力来显示您的数据。 Phyatt 已经为您指出了一些方向,但我会尝试更具体:使用Vertex Buffer Object (VBO)。

这个想法是预先分配所需的内存以在 GPU 上显示您的数据,并在需要时更新这块内存。这可能会对代码的效率产生巨大影响,因为您将使用高效的视频卡驱动程序来处理 CPU->GPU 传输。

为了说明这个概念,我将在答案的末尾向您展示一些伪代码,但这绝不是完全正确的。我没有测试,也没有时间为你实现绘图,但它是一个可以让你理清思路的概念。

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;
 

最后一点,我不能向您保证这是最有效的做事方式。可能,如果您有大量数据,您可能需要一些数据结构,例如 Octrees 或 scene-graphs 来优化您的代码。

我建议您查看OpenSceneGraph 或Visualization ToolKit,看看这些方法是否还没有为您实现,什么可以为您节省大量时间。

【讨论】:

谢谢。我会尝试实现你的方法并让你知道! @SamerAfach 不要忘记查看推荐的库! OpenSceneGraph 有一些不错的绘图方法!我没有使用 Visualization Toolkit 的经验,但它是 PCL 用来渲染法线的库,女巫与您的问题直接相关! :) 其实我用 gluCylinder(quadObj, D, D, L-4*D, 32, 1);创建圆柱体。我不能缓冲四边形对象而不是缓冲顶点吗? 没有。据我所知,glu 使用立即模式来渲染他们的二次曲线。通过一些改进的搜索,您可能会找到一些已经生成有序 cilinder 顶点的代码。您甚至可以查看 gluCylinder 代码来自己实现它。你看过推荐的库吗? 我检查了它们。它们看起来很复杂,需要很长时间才能更改我的程序以适应它们。我想继续使用原始 OpenGL。谢谢你的建议,我会继续努力的:)【参考方案2】:

试试这个链接以获得一些想法:

What are some best practices for OpenGL coding (esp. w.r.t. object orientation)?

基本上,我看到人们为提高 FPS 和掉落质量所做的工作包括以下内容:

使用显示列表。 (缓存复杂或重复的矩阵堆栈)。

使用顶点数组。

使用更少面的更简单的几何体。

使用更简单的照明。

使用更简单的纹理。

OpenGL 的主要优点是可以与很多显卡一起使用,这些显卡可以非常快速地执行大量 4x4 矩阵转换、乘法等,并且它们提供更多 RAM 内存来存储渲染或部分渲染对象。

假设所有的向量都发生了如此大的变化并且经常发生你无法缓存任何渲染的情况......

我解决此问题的方法是将绘图简化为仅线和点,并使其以所需的帧速率进行绘制。 (圆柱体的线条和方向末端的彩色点。)

在画得足够快之后,尝试使绘图更复杂,比如用矩形棱柱代替直线,用金字塔代替彩色点。

圆形物体通常需要更多的表面和计算。

我不是这方面的专家,但我会在谷歌上搜索其他处理优化的 OpenGL 教程。

希望对您有所帮助。

编辑:由于 cmets,删除了对 NeHe 教程的引用。

【讨论】:

另外,他应该考虑使用顶点数组。 哦,是的,我忘记了这些。我会把它们放进去。 通常是 openGl,还有另一种方便的方法...顶点缓冲区:songho.ca/opengl/gl_vbo.html 从未使用过这些,但听起来很有希望,对吧? @NicolBolas 你推荐什么 OpenGL 参考? @NicolBolas,很好奇,NeHe 有什么问题?它过时了吗?方法不对吗?您希望 *** 包含所有答案吗?

以上是关于使用 OpenGL 绘图而不杀死 CPU 并且不并行化的主要内容,如果未能解决你的问题,请参考以下文章

主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程

绘图调用期间 Opengl vao 中断

sh 在一秒钟内重复评估CPU占用最多的CPU(至少保持一个核心忙于100%)并且杀死p

在 OpenGL ES 中,如何将绘图“剪辑”到矩形?

为啥这个 UPDATE 查询会杀死我的 CPU?

Android OpenGL学习笔记