Search code examples
qtqt6qtopengl

QPainter effects were removed when rendering a QOpenGLWidget into a QPixmap


I have a QOpenGLWidget that does some OpenGL rendering and some non-OpenGL rendering using a QPainter. The widget looks OK but when I try to create a screenshot of the widget by rendering it into a QPixmap, suddenly all effects that are painted using QPainter disappear. Here is a minimal code sample to reproduce the issue:

#include <QApplication>
#include <qopenglwidget.h>
#include <qopenglfunctions.h>
#include <qpushbutton.h>
#include <QPainter>

class MainWidget : public QOpenGLWidget, QOpenGLFunctions{
public:

    QPushButton* screenshot_button;

    MainWidget(QWidget* parent=nullptr) : QOpenGLWidget(parent){
        screenshot_button = new QPushButton("sreenshot", this);
        QObject::connect(screenshot_button, &QPushButton::clicked, [this](){
            take_screenshot();
        });
    }

    void initializeGL(){
        initializeOpenGLFunctions();
    }

    void take_screenshot(){
        QPixmap pixmap(size());
        render(&pixmap, QPoint(), QRegion(rect()));
        pixmap.save("screenshot.png");
    }

    void paintGL(){
        QPainter painter(this);

        painter.beginNativePainting();
        glClearColor(0.80, 0.80, 0.80, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        painter.endNativePainting();

        // this disappears when the screenshot button is pressed!
        // also it is not present in the screenshot
        painter.drawRect(QRect(0, 0, 100, 100));
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWidget w;
    w.show();
    return a.exec();
}

Before pressing the button the widget looks normal: screenshot1.png

But after pressing the screenshot button, the rectangle disappears from the widget (also it is absent from screenshot.png) until I resize the window which forces a re-render. screenshot.png

I am using qt 6.5 on windows 10.


Solution

  • Solution 1:

    From QScreen::grabWindow() documentation:

    The grabWindow() function grabs pixels from the screen, not from the window, i.e. if there is another window partially or entirely over the one you grab, you get pixels from the overlying window, too.

    Meaning it grabs from the framebuffer unlike QWidget::grab and QWidget::render which ask the widget to render itself.

    So it could be used instead of render() to avoid the problems caused by combining the latter with OpenGL:

    void take_screenshot()
    {     
        QGuiApplication::primaryScreen()->grabWindow(winId()).save("screenScreenshot.png");
    }
    

    Solution 2:

    Less straightforward but just in case render is a must.

    Use a bool to disable openGL when calling render in take_screenshot(), and make sure you call grabFramebuffer() before.

    void take_screenshot()
    {
        grabFramebuffer();
    
        QPixmap pixmap(size());
    
        enable_opengl=false;
        render(&pixmap, QPoint(), QRegion(rect()));
        enable_opengl=true;
    
        pixmap.save("renderScreenshot.png");
    }
    
    void paintGL()
    {
        QPainter painter(this);
    
        if(enable_opengl)
        {
            glClearColor(0.80, 0.80, 0.80, 1);
            glClear(GL_COLOR_BUFFER_BIT);
        }
    
        painter.fillRect(QRect(10, 10, 100, 100), Qt::green);
    }