1

I am using QOpenGLWidget and can't understand where should I put drawing code: inside overriden paintGL or inside overriden paintEvent.

Should I call base class versions of these functions?

How these functions are connected? paintGL launches paintEvent or vice-versa? Maybe they launch as a result of non intersecting reasons (i.e. restore window, draw some 3D geometry, change window size)? What are these reasons then?

Finally, how to force rerender of graphics when I change geometry? What method should I call?

1 Answers1

2

The short answer: Open GL drawing in QOpenGLWidget should happen in QOpenGLWidget::paintGL().

When OpenGL commands shall be called, a pre-condition is that the resp. OpenGL context has been activated before. This is what QOpenGLWidget::paintGL() ensures:

There is no need to call makeCurrent() because this has already been done when this function is called.

Before invoking this function, the context and the framebuffer are bound, and the viewport is set up by a call to glViewport().

Btw. another pre-condition is that the resp. OpenGL context has been created at all.


To find out more about this I digged a bit deeper – in QOpenGLWidget::paintEvent() (on woboq.org):

void QOpenGLWidget::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e);
    Q_D(QOpenGLWidget);
    if (!d->initialized)
        return;
    if (updatesEnabled())
        d->render();
}
  1. The paint event does nothing as long as initialization is not yet done. (I didn't dig deeper but I'm sure that initialization involves calling of QOpenGLWidget::initializeGL().)

  2. The paint event requests rendering.

Following the code by eyes (stricly speaking: mouse clicks), d->render() calls QOpenGLWidgetPrivate::render() which in turn calls finally QOpenGLWidgetPrivate::invokeUserPaint() and here we are:

void QOpenGLWidgetPrivate::invokeUserPaint()
{
    Q_Q(QOpenGLWidget);
    QOpenGLContext *ctx = QOpenGLContext::currentContext();
    Q_ASSERT(ctx && fbo);
    QOpenGLFunctions *f = ctx->functions();
    QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbo->handle();
    f->glViewport(0, 0, q->width() * q->devicePixelRatioF(), q->height() * q->devicePixelRatioF());
    inPaintGL = true;
 // vvvvvvvvvvvv
    q->paintGL();
 // ^^^^^^^^^^^^
    inPaintGL = false;
    flushPending = true;
    QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
}

(The comments are mine.)

So, if QOpenGLWidget::paintEvent() is overloaded then it should call the paintEvent() of base class. (Otherwise, the OpenGL rendering will certainly break.)


Finally, how to force rerender of graphics when I change geometry? What method should I call?

This is actually answered in the description on QOpenGLWidget:

If you need to trigger a repaint from places other than paintGL() (a typical example is when using timers to animate scenes), you should call the widget's update() function to schedule an update.


In case, I misunderstood the intention of OP, and the actual question was where to put QPainter drawing in QOpenGLWidget – I once wrote an answer to SO: Paint a rect on qglwidget at specifit times regarding mixing OpenGL commands and QPainter drawing in paintGL().

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • 1
    Great answer! Now it is clear that when I want to redraw my geometry I can simply call update(), it will call paintEvent(), which finally will call paintGL(). –  Jun 14 '18 at 12:41
  • Yepp, exactly. I was just about to add this part. And it does also mean that window resizing, overlapping, etc. should force updates as well (as usual). – Scheff's Cat Jun 14 '18 at 12:43