3

I am experimenting with the new QOpenGLWidget class (note that this is not the QGLWidget class).

I am drawing a triangle. I have a trivial vertex shader which receives coordinates in clip space, so no matrices or projections are involved. One of the vertices has coordinates -1, -1, 0, 1, and another one has coordinates 1, 1, 0, 1.

When I have no call to glViewport whatsoever, the program renders as if I am calling glViewport(0, 0, w, h); in my resizeGL function, which I am not. Namely, the two vertices of the triangle are attached to the lowerleft and upperright corners of the window no matter how I resize the window.

When I actually add a call to glViewport in my resizeGL function, it is apparently ignored - doesn't matter if I pass w/2, h/2 or any other value, the rendering is exactly the same as it would be if I called glViewport(0, 0, w, h); (for instance, I would expect the triangle to appear in the lower-left quarter of the window in case of glViewport(0, 0, w/2, h/2);)

When I call glViewport(0, 0, width()/2, height()/2) in paingGL function, the rendering is as expected - everything is drawn in the lower-left quarter of the window.

So it seems that the glViewport is overridden somewhere between resizeGL and paintGL. What is going on and how do I fix it? Do I have to resort to doing viewport transformations in my paintGL function?

One of the differences between QGLWidget and QOpenGLWidgets listed in the documentation is that the latter renders to a framebuffer rather than directly to the screen. Could this hold the key to the explanation?

Just in case, I'm attaching the complete code for reference.

//triangle.h

#ifndef TRIANGLE_H
#define TRIANGLE_H

#include <QOpenGLBuffer>
#include <QOpenGLFunctions>

class Triangle
{
public:
    Triangle();
    void render();
    void create();

private:
    QOpenGLBuffer position_vbo;
    QOpenGLFunctions *glFuncs;
};

#endif // TRIANGLE_H

//triangle.cpp

#include "triangle.h"

Triangle::Triangle()
    :position_vbo(QOpenGLBuffer::VertexBuffer)
{    

}

void Triangle::create()
{
    glFuncs = QOpenGLContext::currentContext()->functions();
    position_vbo.create();
    float val[] = {
           -1.0f,   -1.0f, 0.0f, 1.0f,
            0.0f, -0.366f, 0.0f, 1.0f,
            1.0f,    1.0f, 0.0f, 1.0f,
            1.0f,    0.0f, 0.0f, 1.0f,
            0.0f,    1.0f, 0.0f, 1.0f,
            0.0f,    0.0f, 1.0f, 1.0f,
        };
    position_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
    position_vbo.bind();
    position_vbo.allocate(val, sizeof(val));
    position_vbo.release();
}

void Triangle::render()
{
    position_vbo.bind();
    glFuncs->glEnableVertexAttribArray(0);
    glFuncs->glEnableVertexAttribArray(1);
    glFuncs->glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glFuncs->glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)(3*4*sizeof(float)));
    glFuncs->glDrawArrays(GL_TRIANGLES, 0, 3);
    glFuncs->glDisableVertexAttribArray(0);
    glFuncs->glDisableVertexAttribArray(1);
    position_vbo.release();
}

//widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>

#include "triangle.h"

class Widget : public QOpenGLWidget
             , protected QOpenGLFunctions
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);    
    ~Widget();

protected:
    virtual void initializeGL() override;
    virtual void paintGL() override;
    virtual void resizeGL(int w, int h) override;
private:
    QOpenGLShaderProgram* program;
    Triangle t;
};

#endif // WIDGET_H

//widget.cpp

#include "widget.h"
#include <exception>
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
}

Widget::~Widget()
{

}

void Widget::initializeGL()
{
    initializeOpenGLFunctions();
    program = new QOpenGLShaderProgram(this);
    if(!program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertexshader.vert"))
    {
       throw std::exception(("Vertex Shader compilation error: " + program->log()).toLocal8Bit().constData());
    }
    if(!program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragmentshader.frag"))
    {
       throw std::exception(("Fragment Shader compilation error: " + program->log()).toLocal8Bit().constData());
    }
    if(!program->link())
    {
       throw std::exception(("Program Link error: " + program->log()).toLocal8Bit().constData());
    }

    t.create();
}


void Widget::paintGL()
{
    glClearColor(0.f, 0.15f, 0.05f, 0.f);
    glClear(GL_COLOR_BUFFER_BIT);
    //glViewport(0, 0, width()/2, height()/2); //works!!
    program->bind();
    t.render();
    program->release();
}

void Widget::resizeGL(int w, int h)
{
    glViewport(0, 0, w/2, h/2); //doesn't work
}

//main.cpp

#include "widget.h"

#include <exception>

#include <QApplication>
#include <QMessageBox>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    try
    {
        Widget w;
        w.show();
        return a.exec();
    }
    catch(std::exception const & e)
    {
        QMessageBox::warning(nullptr, "Error", e.what());
    }
}

//vertex shader

#version 330
layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;

smooth out vec4 theColor;

void main()
{
    gl_Position = position;
    theColor = color;
}

//fragment shader

#version 330
out vec4 fragColor;
smooth in vec4 theColor;
void main()
{
    fragColor = theColor;
}
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • As far as I know, QOpenGLWidget binds the internal framebuffer (id=1) and also sets the viewport to the framebuffers size before it calls paintGL. – BDL Oct 11 '15 at 00:10

1 Answers1

2

So it seems that the glViewport is overridden somewhere between resizeGL and paintGL. What is going on and how do I fix it? Do I have to resort to doing viewport transformations in my paintGL function?

Qt5 may use OpenGL for its own drawing. Also the content of widgets being children to a QOpenGLWindow are rendered to FBOs. So that means, that a lot of glViewport calls are made between your code and what Qt does.

When I actually add a call to glViewport in my resizeGL function, it is apparently ignored (…)

yes. And your expectation is what exactly? The only valid place to call glViewport for a OpenGL program to be robust is in the drawing code. Each and every tutorial out there, that places glViewport in the window resize handler is wrong and should be burned.

When I call glViewport(0, 0, width()/2, height()/2) in paingGL function, the rendering is as expected

Yes, that's how you're supposed to use it.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • 1
    Well, I'm only happy that calling glViewport in paintGL is good. I thought it wasn't a good idea to do it on every frame, because it appears all the tutorials I've ever seen should be burned. So are you saying that the perspective matrix should also be set up in paintGL? – Armen Tsirunyan Oct 11 '15 at 16:25
  • @ArmenTsirunyan: Yes, it's indeed the case, that you should always perform full state setup for every frame. You have to realize, that setting the matrix itself, or perform some manipulation on it, comes with no GPU cost whatsoever (it just consumes a little bit of CPU time). And when it comes to actually drawing stuff, the matrix is loaded into GPU uniform registers anyway. – datenwolf Oct 11 '15 at 17:23
  • @ArmenTsirunyan I agree with datenwolf: You have to share the context with Qt itself (unless you create your own shared context). Personnaly I setup everything (viewport, matrices, blending options, textures options,...) in paintGL with no perfromance penalty. Remember that OpenGL is a state machine and that developpers rarely restitute the state as they found it at the entry point of their functions! – Bertrand Apr 02 '16 at 15:18
  • It's actually a pain to restitute the state in general. You don't know what has been enabled, bound etc. by other code, especially if it's related to some extensions which don't even know about. So in general it's much more robust to use your own context which you have full control of, instead of hoping that other code hasn't broken the default one. – Ruslan Sep 04 '16 at 07:31