PyQt5 OpenGL swapBuffers 非常慢

PyQt5 OpenGL swapBuffers very slow

我正在尝试使用 PyQt5 和 PyOpenGL 制作一个小型应用程序。一切正常,但渲染时间太长,即使只有一个球体。我尝试了不同的途径来尝试优化应用程序的速度,现在我正在使用带有 OpenGLSurface 的简单 QWindow。

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

现在我的问题如下:这正常吗?如果是这样,有没有办法加快这个过程,或者这是否与 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()

我直接使用 OpenGl 而没有使用 Qt opengl 定义,表面的格式如下:

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

编辑1: 关于我的代码如何工作的更多说明:

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个细分来渲染一个球体,我也删除了重复的顶点,但这没有做任何事情,因为我认为这真的不应该是瓶颈)。

回答一些问题:我确实尝试过各种不同版本的 opengl 只是为了 gigglez,没有变化,没有 vsync 的情况下尝试过,没有变化,尝试过不同的样本大小,没有变化。

编辑2: 可能是一个线索:大部分时间 swapBuffers 大约需要 0.015 秒,但是当我开始大量移动时,它会断断续续地跳到某些渲染的 0.05 秒。为什么会这样?据我了解,无论如何,每个渲染器都必须处理所有数据?

按照OpenGL的工作方式,你提交的渲染命令被发送到GPU并异步执行(坦白说,即使是发送到GPU的过程也是异步的)。当您通过调用 swapBuffers 请求显示后台缓冲区时,显示驱动程序必须等待后台缓冲区的内容完成渲染(即所有先前发出的命令完成执行),然后它才能交换缓冲区。

如果你遇到低帧率那么你应该优化你的渲染代码,这是你提交给 GPU 的东西。切换到 C++ 在这里对你没有帮助(尽管它独立是一个好主意)。

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

注意: 您调用 swapBuffers 两次,一次在 render() 中,一次在 renderNow() 中。

免责声明:我不熟悉 PyOpenGL。


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