Do offscreen render(openGL) with Qt5



  • Using openGL to do some image processing, the first experiment is
    convert the color image to gray, everything are fine, but I don't want
    to show the widget.

    If I don't call "show()" the QGLWidget would not begin to render the texture
    Could I render the texture without showing the widget?
    Is QGLWidget a right tool to do that?

    part of the codes

    @
    #include <QDebug>

    #include "toGray.hpp"

    toGray::toGray(std::string const &vertex_file, std::string const &fragment_file, QWidget *parent)
    :basicGLWidget(vertex_file, fragment_file, parent) //read shaders, compile them, allocate VBO
    {
    }

    void toGray::initializeVertexBuffer()
    {
    std::vector<GLfloat> const vertex{
    -1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    -1.0f, -1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, -1.0f, 0.0f, 1.0f,
    -1.0f, -1.0f, 0.0f, 1.0f,
    };

    initializeVertexBufferImpl(vertex); //copy the data into QOpenGLBuffer
    
    QImage img(":/simpleGPGPU/images/emili.jpg");
    texture_addr_ = bindTexture(img);
    resize(img.width(), img.height());
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    

    }

    void toGray::paintGL()
    {
    qglClearColor(Qt::white);
    glClear(GL_COLOR_BUFFER_BIT);

    program_.bind();
    bind_buffer();
    program_.enableAttributeArray("qt_Vertex");
    program_.setAttributeBuffer( "qt_Vertex", GL_FLOAT, 0, 4);
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture_addr_);
    
    glDrawArrays(GL_TRIANGLES, 0, get_buffer(0).size());
    
    program_.disableAttributeArray("qt_Vertex");
    program_.release();
    glActiveTexture(0);
    release_buffer();
    

    }

    @

    vertex shader
    @
    attribute highp vec4 qt_Vertex;
    varying highp vec2 qt_TexCoord0;

    void main(void)
    {
    gl_Position = qt_Vertex;
    qt_TexCoord0 = (qt_Vertex.xy + vec2(1.0)) * 0.5;
    }

    @

    fragment shader
    @
    uniform sampler2D source;
    varying highp vec2 qt_TexCoord0;

    vec3 toGray(vec3 rgb)
    {
    return vec3(rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114);
    }

    void main(void)
    {
    vec3 gray = toGray(texture2D(source, qt_TexCoord0).rgb);
    gl_FragColor = vec4(gray, 0.0);
    }
    @



  • You have to work directly on OpenGL context.

    https://www.opengl.org/wiki/Creating_an_OpenGL_Context_(WGL)
    https://qt-project.org/doc/qt-4.8/qglcontext.html

    Do render frame you have to call "makeCurrent()":https://qt-project.org/doc/qt-4.8/qglcontext.html#makeCurrent at the beginning and "doneCurrent()":https://qt-project.org/doc/qt-4.8/qglcontext.html#doneCurrent in the end.

    Hope i'm right.



  • Thanks, could you introduce me some examples?
    I want to process an image by openGL and save the image after process
    without showing the widget.

    For me, the threshold of openGL is pretty high, the rules are complicated



  • Sorry that I could not give you straight example of that but there are many tutorials about rendering frame to texture on the web:

    http://lmgtfy.com/?q=opengl+render+to+texture

    One good and easy example:

    http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture/

    To get texture pixels back from GPU to RAM (so that you can create QImage) you have to use "glReadPixels":https://www.opengl.org/sdk/docs/man/xhtml/glReadPixels.xml (read pixels from currently bound frame buffer) or "glGetTexImage":https://www.opengl.org/sdk/docs/man/xhtml/glGetTexImage.xml (to read pixels from currently bound texture). Don't be surprised when obtained image is flipped upside down (you have to fix it manually ). Pixels might have mismatched channels, QImage does not support GL_RGBA (maybe combination of GL_RGB and QImage::Format_RGB888 could work).

    bq. For me, the threshold of openGL is pretty high, the rules are complicated

    Probably not. Only conversion from texture to QImage might course a little trouble. I strongly recommend you to use scanLine instead of setPixel.
    Rules are sometimes complicated yes, you sometimes cannot read pixels from GPU because you graphics card does not support given format (or do software processing instead).



  • Using QWindow to do some offscreen rendering, but the function
    "initializeOpenGLFunctions();" always make the program crash

    .hpp
    @
    #ifndef OFFSCREENEXP_HPP
    #define OFFSCREENEXP_HPP

    #include <QOpenGLFunctions>
    #include <QWindow>

    class QOpenGLContext;

    class offScreenExp : public QWindow, protected QOpenGLFunctions
    {
    public:
    explicit offScreenExp(QWindow *parent = 0);

    private:
    QOpenGLContext *context_;
    };

    #endif // OFFSCREENEXP_HPP

    @

    .cpp
    @
    #include <QOpenGLContext>

    #include <QDebug>

    #include "offScreenExp.hpp"

    offScreenExp::offScreenExp(QWindow *parent) :
    QWindow(parent),
    context_(nullptr)
    {
    setSurfaceType(QWindow::OpenGLSurface);

    if (!context_) {
        context_ = new QOpenGLContext(this);
        context_->setFormat(requestedFormat());
    
        if (!context_->create())
            qFatal("Cannot create the requested OpenGL context!");
    
    
        qDebug()<<"create context";
    }
    
    context_->makeCurrent(this);
    
    qDebug()<<"initialize gl func";
    initializeOpenGLFunctions(); //crash at here
    qDebug()<<"after initialize gl func";
    

    }

    @



  • Looks like the problem is I haven't called "create()" in my codes
    Now the codes can compile, but the QImage return by the QOpenGLFrameBufferObject is empty.

    .hpp
    @
    #ifndef OFFSCREENEXP_HPP
    #define OFFSCREENEXP_HPP

    #include <QOpenGLFunctions>
    #include <QWindow>

    class QImage;
    class QOpenGLContext;

    class offScreenExp : public QWindow, protected QOpenGLFunctions
    {
    public:
    explicit offScreenExp(QWindow *parent = 0);

    QImage render();
    

    private:
    QOpenGLContext *context_;
    };

    #endif // OFFSCREENEXP_HPP

    @

    .cpp
    @
    #include <QImage>
    #include <QOpenGLBuffer>
    #include <QOpenGLContext>
    #include <QOpenGLFramebufferObject>
    #include <QOpenGLShaderProgram>
    #include <QString>
    #include <QWidget>

    #include <QDebug>

    #include "offScreenExp.hpp"

    offScreenExp::offScreenExp(QWindow *parent) :
    QWindow(parent),
    context_(nullptr)
    {
    setSurfaceType(QWindow::OpenGLSurface);
    setFormat(QSurfaceFormat());
    create();
    }

    QImage offScreenExp::render()
    {
    //create the context
    if (!context_) {
    context_ = new QOpenGLContext(this);
    QSurfaceFormat format;
    context_->setFormat(format);

        if (!context_->create())
            qFatal("Cannot create the requested OpenGL context!");
    }
    
    context_->makeCurrent(this);
    initializeOpenGLFunctions();
    
    //load image and create fbo
    QString const prefix("/Users/Qt/program/experiment_apps_and_libs/openGLTest/simpleGPGPU/");
    QImage img(prefix + "images/emili.jpg");
    if(img.isNull()){
      qFatal("image is null");
    }
    QOpenGLFramebufferObject fbo(img.size());
    qDebug()<<"bind success? : "<<fbo.bind();
    
    //if(glCheckFramebufferStatus(fbo.handle()) != GL_FRAMEBUFFER_COMPLETE){
    //    qDebug()<<"frame buffer error";
    //}
    qDebug()<<"has opengl fbo : "<<QOpenGLFramebufferObject::hasOpenGLFramebufferObjects();
    
    //use two triangles two cover whole screen
    std::vector<GLfloat> const vertex{
        -1.0f,  1.0f, 0.0f, 1.0f,
        1.0f,  1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 1.0f,
    };
    
    //initialize vbo
    QOpenGLBuffer buffer(QOpenGLBuffer::VertexBuffer);
    buffer.create();
    buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
    buffer.bind();
    buffer.allocate(&vertex[0], vertex.size() * sizeof(GLfloat) );
    buffer.release();
    
    //create texture
    GLuint rendered_texture;
    glGenTextures(1, &rendered_texture);
    
    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, rendered_texture);
    
    //naive solution, better encapsulate the format in a function
    if(img.format() == QImage::Format_Indexed8){
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, img.width(), img.height(), 0, GL_RED, GL_UNSIGNED_BYTE, img.scanLine(0));
    }else if(img.format() == QImage::Format_RGB888){
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width(), img.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, img.scanLine(0));
    }else{
        QImage temp = img.convertToFormat(QImage::Format_RGB888);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width(), img.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, temp.scanLine(0));
    }
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    glBindTexture(GL_TEXTURE_2D, 0);
    
    //compile and link program
    QOpenGLShaderProgram program;
    if(!program.addShaderFromSourceCode(QOpenGLShader::Vertex,
                                        "attribute highp vec4 qt_Vertex;"
                                        "varying highp vec2 qt_TexCoord0;"
    
                                        "void main(void)"
                                        "{"
                                        "   gl_Position =  qt_Vertex;"
                                        "   qt_TexCoord0 = (qt_Vertex.xy + vec2(1.0)) * 0.5;"
                                        "}")){
        qDebug()<<"QOpenGLShader::Vertex error : " + program.log();
    }
    
    // Compile fragment shader
    if (!program.addShaderFromSourceCode(QOpenGLShader::Fragment,
                                         "uniform sampler2D source;"
                                         "varying highp vec2 qt_TexCoord0;"
    
                                         "vec3 toGray(vec3 rgb)"
                                         "{"
                                         " return vec3(rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114);"
                                         "}"
    
                                         "void main(void)"
                                         "{"
                                         "vec3 gray = toGray(texture2D(source, qt_TexCoord0).rgb);"
                                         "gl_FragColor = vec4(gray, 0.0);"
                                         "}"
                                         )){
        qDebug()<<"QOpenGLShader::Fragment error : " + program.log();
    }
    
    // Link shader pipeline
    if (!program.link()){
        qDebug()<<"link error : " + program.log();
    }
    
    //render the texture as usual
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glViewport(0, 0, img.width(), img.height());
    
    program.bind();
    buffer.bind();
    program.enableAttributeArray("qt_Vertex");
    program.setAttributeBuffer( "qt_Vertex", GL_FLOAT, 0, 4);
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, rendered_texture);
    
    glDrawArrays(GL_TRIANGLES, 0, buffer.size());
    
    program.disableAttributeArray("qt_Vertex");
    program.release();
    glActiveTexture(0);
    glBindTexture(GL_TEXTURE_2D, 0);
    buffer.release();
    
    return fbo.toImage();
    

    }

    @



  • missing doneCurrent() call in render method ??

    67: buffer.release(); //you should probably remove this line



  • remove line 67 and add doneCurrent at the end

    @
    glActiveTexture(0);
    glBindTexture(GL_TEXTURE_2D, 0);
    buffer.release();

    context_->doneCurrent();
    
    return fbo.toImage();
    

    @

    Still get a blank image



  • Edit the codes of offScreenExp::render part, begin from line 132
    change the order of cleaning resource

    @
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glViewport(0, 0, img.width(), img.height());

    program.bind();
    buffer.bind();
    program.enableAttributeArray("qt_Vertex");
    program.setAttributeBuffer( "qt_Vertex", GL_FLOAT, 0, 4);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, rendered_texture);

    //bind and create fbo
    QOpenGLFramebufferObject fbo(img.size());
    qDebug()<<"bind success? : "<<fbo.bind();

    glDrawArrays(GL_TRIANGLES, 0, buffer.size());
    QImage result = fbo.toImage();

    program.disableAttributeArray("qt_Vertex");
    program.release();
    buffer.release();

    fbo.release();
    glActiveTexture(0);
    glBindTexture(GL_TEXTURE_2D, 0);
    context_->doneCurrent();

    return result;
    @

    But I am still get a blank image


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.