QOpenGLWidget с Qpainter: рисунок OpenGL виден только после первого вызова PaintGL

Я хотел бы использовать QOpenGLWidget в QT для создания быстрых линейных графиков. Эти графики должны быть снабжены некоторыми метками, для чего я сейчас использую QPainter с drawText(). В различных примерах команды OpenGL сначала используются в методе PaintGL, а затем в Qpainter. Например. здесь:

Как использовать QPainter в PaintGL QOpenGlWidget

Собственный код QPainter и OpenGL в классе QOpenGLWidget

Собственный код QPainter и OpenGL в классе QOpenGLWidget

Я написал небольшой пример виджета (C++), в котором строки, а затем текст рисуются в PaintGL (см. ниже). Моя проблема сейчас в том, что и линии, и текст можно увидеть правильно при первом рисовании. Если метод PaintGL вызывается второй раз (например, после изменения размера), по-прежнему виден только текст, линии больше не видны.

Я использую QT 6.4.2 с OpenGL 4.6.0 под Windows 10 с 64-разрядным приложением.

Вот объявление виджета:

    struct ColorVertex_t {
        float x = 0, y = 0, z = 0, r = 0, g = 0, b = 0, a = 0.8f;

        // OpenGL Helpers
        static const int PositionTupleSize = 3;
        static const int ColorTupleSize = 4;
        static Q_DECL_CONSTEXPR inline int positionOffset() { return offsetof(ColorVertex_t, x); }
        static Q_DECL_CONSTEXPR inline int colorOffset() { return offsetof(ColorVertex_t, r); }
        static Q_DECL_CONSTEXPR inline int stride() { return sizeof(ColorVertex_t); }
    };


    class OpenGLLinePlotWidget : public QOpenGLWidget, protected QOpenGLFunctions
    {
        Q_OBJECT
        
        QOpenGLShaderProgram* m_program;
        QOpenGLVertexArrayObject vao_lines_;    // vertex array object for the lines
        unsigned int vbo_lines_ = 0;            // vertex buffer object for the lines, type GL_ARRAY_BUFFER

        /// vector of vertex coordinates and colors (RGB) for the lines
        std::vector<ColorVertex_t> vertices_lines_;


    public:
        OpenGLLinePlotWidget(QWidget* parent = nullptr);
        ~OpenGLLinePlotWidget();

    protected:
        void initializeGL() override;
        void resizeGL(int w, int h) override;
        void paintGL() override;
        
    };

А вот определения:

const char* vertexShaderSourceLine = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout (location = 1) in vec4 aColor; // the color variable has attribute position 1\n"
"\n"
"out vec4 ourColor; // output a color to the fragment shader\n"
"\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"   ourColor = aColor;\n"
"}\0";

const char* fragmentShaderSourceLine = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec4 ourColor;\n"
"void main()\n"
"{\n"
"   FragColor = ourColor;\n"
"}\n\0";


OpenGLLinePlotWidget::OpenGLLinePlotWidget(QWidget* parent) : QOpenGLWidget(parent), m_program(nullptr)
{
    // Define some line coordinates
    size_t num_vertices = 100;
    vertices_lines_.resize(num_vertices);
    ColorVertex_t vertex;
    for (size_t i = 0; i < num_vertices; ++i) {
        vertex.x = -1. + i * 0.016;
        vertex.y = sin(i * 0.314);
        vertices_lines_[i] = vertex;
    }
}

OpenGLLinePlotWidget::~OpenGLLinePlotWidget()
{
    // Make sure the context is current and then explicitly destroy all underlying OpenGL resources.
    makeCurrent();
    // Actually destroy our OpenGL information
    vao_lines_.destroy();
    delete m_program;
    doneCurrent();
}

/* This function is called by OpenGL and must not be called manually! */
void OpenGLLinePlotWidget::initializeGL()
{
    // Initialize OpenGL Backend
    initializeOpenGLFunctions();
    glClearColor(1.f, 1.f, 1.f, 1.0f);  // white background

    // Create Shader (Do not release until VAO is created)
    m_program = new QOpenGLShaderProgram();
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSourceLine);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSourceLine);
    m_program->link();
    m_program->bind();

    // ------------- cursor sample mesh buffers ---------------
    vao_lines_.create();
    vao_lines_.bind();
    glGenBuffers(1, &vbo_lines_);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_lines_);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, ColorVertex_t::stride(), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, ColorVertex_t::stride(), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    vao_lines_.release();
}

void OpenGLLinePlotWidget::resizeGL(int w, int h)
{
}

void OpenGLLinePlotWidget::paintGL()
{
    if (!isValid()) return;
    
    glClear(GL_COLOR_BUFFER_BIT);

    // draw lines with OpenGL
    vao_lines_.bind();
    glLineWidth(2.0f);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_lines_);
    glBufferData(GL_ARRAY_BUFFER, sizeof(ColorVertex_t) * vertices_lines_.size(), vertices_lines_.data(), GL_DYNAMIC_DRAW);
    glDrawArrays(GL_LINE_STRIP, 0, static_cast<GLsizei>(vertices_lines_.size()));
    vao_lines_.release();

    // draw some text with QPainter
    QPainter painter(this);
    painter.drawText(10, 20, "Hello World from QPainter in OpenGL!");
}

Я пробовал следующее:

  • Если инструкции QPainter закомментированы, линии рисуются правильно, даже после изменения размера. К такому неправильному поведению приводит только использование QPainter.
  • Я использовал glGetError и glCheckFramebufferStatus, чтобы проверить, произошли ли какие-либо ошибки — но это было не так.
  • Я определил QPainter перед рисованием OpenGL и установил инструкции OpenGL между BeginNativePainting() и endNativePainting(). Но я не вижу здесь никаких строк (по какой-то причине...).
  • Я реализовал функцию PaintEvent, в которую просто поместил вызов PaintGL().
  • Я установил формат поверхности по умолчанию для всего приложения, а также установил формат поверхности моего виджета после его создания с помощью «нового», но это не помогло.

После долгих поисков, проб и ошибок я в растерянности и буду признателен за любую помощь! Спасибо!

Судя по документации, подход beginNativePainting должен работать. Пробовали ли вы запустить свое приложение под RenderDoc, чтобы увидеть, как QPainter меняет ваше состояние OpenGL?

Botje 20.03.2024 10:31

Ваша реализация paintGL связывает только VAO; попробуйте также перепривязать m_program.

G.M. 20.03.2024 10:36

Не перепривязка m_program была ошибкой — теперь она работает! :-) Большое большое спасибо!

Oliver Nemitz 20.03.2024 10:48

@Botje: Спасибо RenderDoc за подсказку! Я не знал об этом инструменте, и я посмотрю на него!

Oliver Nemitz 20.03.2024 10:57

@Г.М. хорошо подмечено! Не могли бы вы сформулировать это как ответ?

Botje 20.03.2024 11:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

[Перенесено из комментария]

При использовании QPainter изнутри paintGL важно отметить, что базовый код Qt может выполнять все виды операций в текущем контексте OpenGL. Следовательно, важно восстановить все соответствующее состояние. В показанном коде...

vao_lines_.bind();
glLineWidth(2.0f);
glBindBuffer(GL_ARRAY_BUFFER, vbo_lines_);
glBufferData(GL_ARRAY_BUFFER, sizeof(ColorVertex_t) * vertices_lines_.size(), vertices_lines_.data(), GL_DYNAMIC_DRAW);
glDrawArrays(GL_LINE_STRIP, 0, static_cast<GLsizei>(vertices_lines_.size()));
vao_lines_.release();

восстанавливается только ВАО. В этом случае также необходимо восстановить шейдерную программу либо...

m_program->bind();

или...

glUseProgram(m_program->programId());

Еще раз спасибо! Кстати: перепривязав m_program, он теперь также работает с Begin/endNativePainting...

Oliver Nemitz 20.03.2024 11:50

Я только что увидел, что все еще реализовал функцию PaintEvent в своем тестовом коде следующим образом:

void OpenGLLinePlotWidget::paintEvent(QPaintEvent* e) {
   paintGL(); 
}

Без этой реализации ничего не рисуется.

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

Похожие вопросы