Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do offscreen render(openGL) with Qt5

Tags:

c++

qt

opengl

Using openGL to do some image processing, the first experiment is convert the color image to gray, everything are fine except 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);
}

Edit: use QWindow FBO to do offscreen render, but the result is a blank image

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
    //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);

//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;
}

I try my best to simplify the codes, but it is still pretty verbose

like image 696
StereoMatching Avatar asked Jun 20 '13 19:06

StereoMatching


People also ask

What is offscreen rendering?

Offscreen rendering lets you obtain the content of a BrowserWindow in a bitmap, so it can be rendered anywhere, for example, on texture in a 3D scene. The offscreen rendering in Electron uses a similar approach to that of the Chromium Embedded Framework project.


1 Answers

For offscreen rendering, try rendering into a QOpenGLFramebufferObject, which can be converted into a QImage, which in turn can easily be saved to disk.

For that however, you still need a surface to render onto (as required by QOpenGLContext::makeCurrent()), so your only choice is indeed using a QWindow or a QGLWidget to get such a surface.

In Qt 5.1, there will be a new class called QOffscreenSurface, which will probably be most suitable for your usecase. You would use QOffscreenSurface to get a surface for your OpenGL context, and then render into a FBO using QOpenGLFramebufferObject, and then call QOpenGLFramebufferObject::toImage() to get access to the pixels.

like image 52
Thomas McGuire Avatar answered Sep 23 '22 04:09

Thomas McGuire