PyQt5 OpenGL swapBuffers非常慢

时间:2016-09-20 11:16:29

标签: python qt opengl pyqt pyopengl

我正在尝试使用PyQt5和PyOpenGL创建一个小应用程序。一切都很好,但即使只有一个球体,渲染也会花费太长时间。我尝试了不同的路线来尝试优化应用程序的速度,现在我正在使用一个带有OpenGLSurface的简单QWindow。

我设法弄清楚,context.swapBuffers调用需要很长时间才能完成,并且会在大约两倍之间变化。当显示具有一些阴影和240个顶点的1个球体时,0.01s(很好)和0.05s(这是长的方式)。

现在我的问题如下:这是正常的吗?如果是这样,有没有办法加快这个过程,或者这与pyqt如何工作有关,因为它是一个python环绕库?基本上:有没有办法让我继续开发这个程序而不需要学习c ++。这是一个非常简单的应用程序,只需要可视化一些原子结构并能够操纵它。

在使用pyopengl的OpenGL时,是否还有其他gui工具包可用于减少开销?

这是渲染的定义:

def renderNow(self):
    if not self.isExposed():
        return

    self.m_update_pending = False

    needsInitialize = False

    if self.m_context is None:
        self.m_context = QOpenGLContext(self)
        self.m_context.setFormat(self.requestedFormat())
        self.m_context.create()

        needsInitialize = True

    self.m_context.makeCurrent(self)

    if needsInitialize:
        self.m_gl = self.m_context.versionFunctions()
        self.m_gl.initializeOpenGLFunctions()

        self.initialize()

    self.render()

    self.m_context.swapBuffers(self)

    if self.m_animating:
        self.renderLater()

我在不使用Qt opengl定义的情况下直接使用OpenGl,表面的格式由下式给出:

fmt = QSurfaceFormat()
fmt.setVersion(4, 2)
fmt.setProfile(QSurfaceFormat.CoreProfile)
fmt.setSamples(4)
fmt.setSwapInterval(1)
QSurfaceFormat.setDefaultFormat(fmt)

EDIT1: 关于我的代码如何工作的更多说明:

def render(self):
    t1 = time.time()
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    wtvMatrix = self.camera.get_wtv_mat()
    transformMatrix = matrices.get_projection_matrix(60, self.width() / self.height(), 0.1, 30, matrix=wtvMatrix)
    transformMatrixLocation = glGetUniformLocation(self.shader,"transformMatrix")
    glUniformMatrix4fv(transformMatrixLocation,1,GL_FALSE,transformMatrix)
    eye_pos_loc = glGetUniformLocation(self.shader, "eye_world_pos0")
    glUniform3f(eye_pos_loc, self.camera.position[0], self.camera.position[1], self.camera.position[2])

    glDrawElementsInstanced(GL_TRIANGLES,self.num_vertices,GL_UNSIGNED_INT,None,self.num_objects)
    print("drawing took:{}".format(time.time()-t1))
    self.frame+=1
    t1=time.time()
    self.m_context.swapBuffers(self)
    print('swapping buffers took:{}'.format(time.time()-t1))

这是我打电话的唯一drawElementsInstanced。着色器设置如下(抱歉这个烂摊子):

VERTEX_SHADER = compileShader("""#version 410
                        layout(location = 0) in vec3 vertex_position;
                        layout(location = 1) in vec3 vertex_colour;
                        layout(location = 2) in vec3 vertex_normal;
                        layout(location = 3) in mat4 model_mat;
                        layout(location = 7) in float mat_specular_intensity;
                        layout(location = 8) in float mat_specular_power;
                        uniform mat4 transformMatrix;
                        uniform vec3 eye_world_pos0;
                        out vec3 normal0;
                        out vec3 colour;
                        out vec3 world_pos;
                        out float specular_intensity;
                        out float specular_power;
                        out vec3 eye_world_pos;
                        void main () {

                        colour = vertex_colour;
                        normal0 = (model_mat*vec4(vertex_normal,0.0)).xyz;
                        world_pos = (model_mat*vec4(vertex_position,1.0)).xyz;
                        eye_world_pos = eye_world_pos0;
                        specular_intensity = mat_specular_intensity;
                        specular_power = mat_specular_power;
                        gl_Position = transformMatrix*model_mat*vec4(vertex_position,1.0);


                        }""", GL_VERTEX_SHADER)

        FRAGMENT_SHADER = compileShader("""#version 410
                        in vec3 colour;
                        in vec3 normal0;
                        in vec3 world_pos;
                        in float specular_intensity;
                        in float specular_power;
                        in vec3 eye_world_pos;

                        out vec4 frag_colour;

                        struct directional_light {
                            vec3 colour;
                            float amb_intensity;
                            float diff_intensity;
                            vec3 direction;
                        };

                        uniform directional_light gdirectional_light;

                        void main () {

                        vec4 ambient_colour = vec4(gdirectional_light.colour * gdirectional_light.amb_intensity,1.0f);
                        vec3 light_direction = -gdirectional_light.direction;
                        vec3 normal = normalize(normal0);

                        float diffuse_factor = dot(normal,light_direction);


                        vec4 diffuse_colour = vec4(0,0,0,0);
                        vec4 specular_colour = vec4(0,0,0,0);

                        if (diffuse_factor>0){
                            diffuse_colour = vec4(gdirectional_light.colour,1.0f) * gdirectional_light.diff_intensity*diffuse_factor;
                            vec3 vertex_to_eye = normalize(eye_world_pos-world_pos);
                            vec3 light_reflect = normalize(reflect(gdirectional_light.direction,normal));
                            float specular_factor = dot(vertex_to_eye, light_reflect);
                            if(specular_factor>0) {
                                specular_factor = pow(specular_factor,specular_power);
                                specular_colour = vec4(gdirectional_light.colour*specular_intensity*specular_factor,1.0f);
                            }
                        }


                        frag_colour = vec4(colour,1.0)*(ambient_colour+diffuse_colour+specular_colour);
                        }""", GL_FRAGMENT_SHADER)

现在我想要旋转场景时使用的代码如下(相机更新等通常是afaik):

def mouseMoveEvent(self, event):

    dx = event.x() - self.lastPos.x()
    dy = event.y() - self.lastPos.y()
    self.lastPos = event.pos()
    if event.buttons() & QtCore.Qt.RightButton:
        self.camera.mouse_update(dx,dy)
    elif event.buttons()& QtCore.Qt.LeftButton:
        pass

    self.renderNow()

一些最终信息:着色器中所需的所有顶点信息都是通过我在初始化定义中初始化和绑定的vao给出的,不包含太多对象(我只是测试它并使用带有2个细分的二十面体)为了渲染一个球体,我也删除了重复的顶点,但没有做任何事情,因为那真的不应该是我认为的瓶颈)。

回答一些问题:我尝试使用varius不同版本的opengl只是为了gigglez,没有更改,尝试没有vsync,没有任何更改,尝试使用不同的样本大小,没有更改。

EDIT2: 可能是一个线索:swapBuffers大部分时间大约需要0.015秒,但是当我开始大量移动时,它会因为某些渲染而断续续续并跳跃到0.05秒。为什么会这样?根据我的理解,每个渲染都必须处理所有数据?

1 个答案:

答案 0 :(得分:2)

顺便说一下,OpenGL的工作方式,你提交的渲染命令被发送到GPU并异步执行(坦率地说,甚至将它们发送到GPU的过程都是异步的)。当您通过调用swapBuffers请求显示后台缓冲区时,显示驱动程序必须等到后台缓冲区的内容完成呈现(即所有先前发出的命令完成执行),然后才能交换缓冲区。

如果您遇到低帧率,那么您应优化渲染代码,即您提交给GPU的内容。切换到C ++对你没有帮助(虽然这是一个好主意)。

编辑:你说当你什么都不做的时候你的swapBuffers会在0.015秒内执行,这可疑的是1/60秒。这意味着您的渲染代码足够高效,可以以60 FPS渲染,您还没有理由对其进行优化。可能发生的情况是,您从renderNow()调用mouseMoveEvent会导致每秒重新渲染场景超过60次,这是多余的。相反,您应该在renderLater()中致电mouseMoveEvent,并相应地重新构建代码。

注意:您拨打swapBuffers两次,一次在render(),一次在renderNow()之后。

免责声明:我不熟悉PyOpenGL。

swapBuffer也可以异步执行,但即便如此,如果显示驱动程序交换缓冲区的速度超过了渲染速度,最终会阻止swapBuffer调用。