I would like to use a QOpenGLWidget in QT to create fast line plots. These plots are to be provided with some labels, for which I am currently using QPainter with drawText(). In various examples, the OpenGL commands are first used in the paintGL method and then the Qpainter. E.g. here:
How to use QPainter in QOpenGlWidget's paintGL
QPainter and OpenGL native code in QOpenGLWidget class
QPainter and OpenGL native code in QOpenGLWidget class
I have written a small example widget (C++) in which lines and then text are drawn in paintGL (see below). My problem now is that both the lines and the text can be seen correctly the first time they are drawn. If the paintGL method is called a second time (e.g. after resizing), only the text is still visible, the lines are no longer visible.
I am using QT 6.4.2 with OpenGL 4.6.0 under Windows 10 with a 64-bit application.
Here is the declaration of the widget:
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;
};
And here are the definitions:
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!");
}
I have tried the following:
After much searching and trial and error, I'm at a loss and would appreciate any help! Thank you!
[Moved from comment]
When using a QPainter
from within paintGL
it's important to note that the underlying Qt
code can perform all sorts of operations on the current OpenGL context. Hence it's important to restore all relevant state. In the code shown...
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();
only the VAO is restored. In this case it's also necessary to restore the shader program with either...
m_program->bind();
or...
glUseProgram(m_program->programId());