PyQt5 OpenGL swapBuffers очень медленно

Я пытаюсь сделать небольшое приложение, используя PyQt5 и PyOpenGL. Все работает нормально, однако рендеринг занимает слишком много времени даже с одной сферой. Я пробовал разные маршруты, чтобы попытаться оптимизировать скорость приложения, и сейчас я использую простое QWindow с OpenGLSurface.

Мне удалось выяснить, что это вызов context.swapBuffers, который занимает много времени и колеблется между прибл. 0,01 с (что хорошо) и 0,05 с (что очень долго) при отображении 1 сферы с некоторым затенением и 240 вершинами.

Теперь мои вопросы следующие: это нормально? Если да, есть ли способ ускорить этот процесс или это связано с тем, как работает pyqt, поскольку это оболочка вокруг библиотеки? По сути: есть ли способ продолжить разработку этой программы без изучения C++. Это довольно простое приложение, которое просто должно визуализировать некоторую атомную структуру и уметь манипулировать ею.

Есть ли другой графический инструментарий, который я мог бы использовать, чтобы иметь меньше накладных расходов при работе с OpenGL из pyopengl?

Это определение, которое делает рендеринг:

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)

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)

Теперь код, который я использую, когда хочу повернуть сцену, выглядит следующим образом (обновления камеры и т. Д. Выполняются, как обычно, на самом деле):

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 ответ

Решение

Благодаря тому, что OpenGL работает, отправляемые вами команды рендеринга отправляются в графический процессор и выполняются асинхронно (честно говоря, даже процесс отправки их в графический процессор асинхронный). Когда вы запрашиваете отображение заднего буфера при вызове swapBuffers драйвер дисплея должен ждать, пока содержимое заднего буфера завершит рендеринг (т. е. все ранее выполненные команды завершат выполнение), и только тогда он может поменять буферы.

Если у вас низкая частота кадров, вы должны оптимизировать код рендеринга, то есть то, что вы отправляете в графический процессор. Переход на C++ здесь вам не поможет (хотя это было бы неплохо независимо).

РЕДАКТИРОВАТЬ: Вы говорите, что, когда вы ничего не делаете, то ваш swapBuffers выполняется за 0,015 секунды, что подозрительно составляет ~1/60 секунды. Это означает, что ваш код рендеринга достаточно эффективен для рендеринга со скоростью 60 FPS, и у вас пока нет причин его оптимизировать. Вероятно, случается так, что ваш звонок renderNow() от mouseMoveEvent вызывает повторную визуализацию сцены более 60 раз в секунду, что является избыточным. Вместо этого вы должны позвонить renderLater() в mouseMoveEventи соответственно реструктурируйте свой код.

ПРИМЕЧАНИЕ: вы звоните swapBuffers дважды, один раз в render() и однажды в renderNow() незамедлительно после.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я не знаком с PyOpenGL.


swapBuffer может также выполняться асинхронно, но даже в этом случае, если драйвер дисплея меняет местами буферы быстрее, чем вы можете сделать, вы в конечном итоге заблокируете swapBuffer вызов.

Другие вопросы по тегам