使用 QOpenGLWidget 进行离屏渲染的最简单方法

Easiest way for offscreen rendering with QOpenGLWidget

我有一个隐藏的 QOpenGLWidget(Qt 5.4.2,不是 QGLWidget),我想基本上不断地执行 grab() 或 grabFramebuffer() 来获取它的内容(并将其写入磁盘)。小部件在可见时呈现良好,但在隐藏时则不会。如果我执行一个 show() 后跟一个 hide() 调用,它就可以工作。这看起来很奇怪,因为根据文档,QOpenGLWidget 在内部已经渲染到帧缓冲区。实现此目的的最简单方法是什么(如果可能,无需创建另一个帧缓冲区)?
能够使用 QOpenGLWidget 作为其视口并在其中使用自定义 OpenGL 绘制的 QGraphicsItems 捕获屏幕外 QGraphicsView 的奖励积分...

更新 2:corresponding bug in QOpenGLWidget seems to be fixed in Qt 5.10, so I suggest to simply use the class again. Although you might want to wait for this bug 也得到修复...
更新 1:添加了 3,使用自定义的最佳解决方案 QWindow-derived class

1 - QOpenGLWidget
如果隐藏的 QOpenGLWidget 确实分配了一个帧缓冲区(不确定是否会发生这种情况),仍然无法手动绑定它,因为您无法获取缓冲区 ID。此外 none 的必要函数 initializeGL()、resizeGL() 和 paintGL 被调用,并且 none 的函数 grab()、grabFramebuffer 和 render() 正常工作。这是(imo)将小部件绘制到屏幕外的解决方法。设置完所有必要的东西后直接调用 paintGL:

class GLWidget: public QOpenGLWidget
{
public:
    GLWidget(QWidget * parent = nullptr);
private:
    bool m_isInitialized = false;
    QOpenGLFramebufferObject m_fbo = nullptr;
};

void GLWidget::drawOffscreen()
{
        //the context should be valid. make sure it is current for painting
    makeCurrent();
    if (!m_isInitialized)
    {
        initializeGL();
        resizeGL(width(), height());
    }
    if (!m_fbo || m_fbo->width() != width() || m_fbo->height() != height())
    {
        //allocate additional? FBO for rendering or resize it if widget size changed
        delete m_fbo;
        QOpenGLFramebufferObjectFormat format;
        format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
        m_fbo = new QOpenGLFramebufferObject(width(), height(), format);
        resizeGL(width(), height());
    }

    //#1 DOES NOT WORK: bind FBO and render() widget
    m_fbo->bind();
    QOpenGLPaintDevice fboPaintDev(width(), height());
    QPainter painter(&fboPaintDev);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    render(&painter);
    painter.end();
    //You could now grab the content of the framebuffer we've rendered to
    QImage image1 = m_fbo->toImage();
    image1.save(QString("fb1.png"));
    m_fbo->release();
    //#1 --------------------------------------------------------------

    //#2 WORKS: bind FBO and render stuff with paintGL() call
    m_fbo->bind();
    paintGL();
    //You could now grab the content of the framebuffer we've rendered to
    QImage image2 = m_fbo->toImage();
    image2.save(QString("fb2.png"));
    m_fbo->release();
    //#2 --------------------------------------------------------------

    //bind default framebuffer again. not sure if this necessary
    //and isn't supposed to use defaultFramebuffer()...
    m_fbo->bindDefault();
    doneCurrent();
}

void GLWidget::paintGL()
{
    //When doing mixed QPainter/OpenGL rendering make sure to use a QOpenGLPaintDevice, otherwise only OpenGL content is visible!
    //I'm not sure why, because according to the docs (http://doc.qt.io/qt-5/topics-graphics.html) this is supposed to be the same...
    QOpenGLPaintDevice fboPaintDev(width(), height());
    QPainter painter(&fboPaintDev);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    //This is what you'd use (and what would work) if the widget was visible
    //QPainter painter;
    //painter.begin(this);

    //now start OpenGL painting
    painter.beginNativePainting();
    glClearColor(0.5f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    ...
    painter.endNativePainting();
    //draw non-OpenGL stuff with QPainter
    painter.drawText(20, 40, "Foo");
    ...
    painter.end();
}

2 - 带有 QOpenGLWidget 视口的 QGraphicsView
当您为它提供 QOpenGLPaintDevice:

时,这里的 render() 会按预期工作
MainWindow::MainWindow()
{
    scene = new QGraphicsScene;
    hiddenView = new QGraphicsView(scene);
    hiddenGLWidget = new QOpenGLWidget;
    hiddenView->setViewport(hiddenGLWidget);
    //hiddenView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    //hiddenView->show();
}

void MainWindow::screenshot()
{
    //try regular grab functions
    QPixmap pixmap1 = hiddenView->grab(); //image with scrollbars, no OpenGL content
    pixmap1.save("bla1.png");
    QPixmap pixmap2 = hiddenGLWidget->grab(); //produces an empty image
    pixmap2.save("bla2.png");
    //try grabbing only the QOpenGLWidget framebuffer
    QImage image1 = hiddenGLWidget->grabFramebuffer(); //null image
    image1.save("bla3.png");

    //WORKS: render via FBO
    hiddenGLWidget->makeCurrent();
    QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    QOpenGLFramebufferObject * fbo = new QOpenGLFramebufferObject(hiddenView->width(), hiddenView->height(), format);
    fbo->bind();
    QOpenGLPaintDevice fboPaintDev(hiddenView->width(), hiddenView->height());
    QPainter painter(&fboPaintDev);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    hiddenView->render(&painter); //WORKS and captures mixed OpenGL and non-OpenGL QGraphicsitems
    //hiddenView->repaint(); //does not work
    //hiddenView->scene()->render(&painter); //does not work
    //hiddenGLWidget->paintGL(); //might work. can not call, protected
    //hiddenGLWidget->render(&painter); //does not work
    //hiddenGLWidget->repaint(); //does not work
    painter.end();
    QImage image2 = fbo->toImage();
    image2.save("bla4.png");
    fbo->release();
    delete fbo;
}

3 - 如何从隐藏的 QOpenGLWidget 渲染和抓取图像
更好的整体解决方案是使用具有 QSurface::OpenGLSurface 类型的自定义 QWindow。创建一个额外的 QOpenGLContext,一个您将绘制到的额外背景 QOpenGLFramebufferObject,以及一个 QOpenGLShaderProgram 以将帧缓冲区 blit 到后台缓冲区。如果你想要多重采样,你可能还需要一个解析 QOpenGLFramebufferObject,将多重采样帧缓冲区转换为 non-multisampled 帧缓冲区。 class接口可以类似于QOpenGLWidget(为用户虚拟initializeGL()、resizeGL()、paintGL())。重新实现 exposeEvent()、resizeEvent() 和 event()(您可能也需要重新实现 metric())。
semi-complete 实现:

Header:

#pragma once

#include <QtCore/QObject>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtGui/QPaintEvent>
#include <QtGui/QResizeEvent>
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLFunctions_3_0>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QSurfaceFormat>
#include <QtWidgets/QWidget>

#include <atomic>
#include <mutex>

class MyGLWindow : public QWindow
{
    Q_OBJECT

public:
    /// @brief Constructor. Creates a render window.
    /// @param targetScreen Target screen.
    /// this is because before the FBO and off-screen surface haven't been created.
    /// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen surface.
    explicit MyGLWindow(QScreen * targetScreen = nullptr);

    /// @brief Constructor. Creates a render window.
    /// @param parent Parent window.
    /// this is because before the FBO and off-screen surface haven't been created.
    /// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen surface.
    explicit MyGLWindow(QWindow * parent);

    /// @brief Destructor.
    virtual ~MyGLWindow();

    /// @brief Create a container widget for this window.
    /// @param parent Parent widget.
    /// @return Returns a container widget for the window.
    QWidget * createWidget(QWidget * parent = nullptr);

    /// @brief Check if the window is initialized and can be used for rendering.
    /// @return Returns true if context, surface and FBO have been set up to start rendering.
    bool isValid() const;

    /// @brief Return the context used in this window.
    /// @return The context used in this window or nullptr if it hasn't been created yet.
    QOpenGLContext * context() const;

    /// @brief Return the OpenGL function object that can be used the issue OpenGL commands.
    /// @return The functions for the context or nullptr if it the context hasn't been created yet.
    QOpenGLFunctions * functions() const;

    /// @brief Return the OpenGL off-screen frame buffer object identifier.
    /// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created yet.
    /// @note This changes on every resize!
    GLuint framebufferObjectHandle() const;

    /// @brief Return the OpenGL off-screen frame buffer object.
    /// @return The OpenGL off-screen frame buffer object or nullptr if no FBO has been created yet.
    /// @note This changes on every resize!
    const QOpenGLFramebufferObject * getFramebufferObject() const;

    /// @brief Return the OpenGL off-screen frame buffer object identifier.
    /// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created yet.
    void bindFramebufferObject();

    /// @brief Return the current contents of the FBO.
    /// @return FBO content as 32bit QImage. You might need to swap RGBA to BGRA or vice-versa.
    QImage grabFramebuffer();

    /// @brief Makes the OpenGL context current for rendering.
    /// @note Make sure to bindFramebufferObject() if you want to render to this widgets FBO.
    void makeCurrent();

    /// @brief Release the OpenGL context.
    void doneCurrent();

    /// @brief Copy content of framebuffer to back buffer and swap buffers if the surface is double-buffered.
    /// If the surface is not double-buffered, the frame buffer content is blitted to the front buffer.
    /// If the window is not exposed, only the OpenGL pipeline is glFlush()ed so the framebuffer can be read back.
    void swapBuffers();

    public slots:
    /// @brief Lazy update routine like QWidget::update().
    void update();
    /// @brief Immediately render the widget contents to framebuffer.
    void render();

signals:
    /// @brief Emitted when swapBuffers() was called and bufferswapping is done.
    void frameSwapped();
    /// @brief Emitted after a resizeEvent().
    void resized();

protected:
    virtual void exposeEvent(QExposeEvent *e) override;
    virtual void resizeEvent(QResizeEvent *e) override;
    virtual bool event(QEvent *e) override;
    //      virtual int metric(QPaintDevice::PaintDeviceMetric metric) const override;

    /// @brief Called exactly once when the window is first exposed OR render() is called when the widget is invisible.
    /// @note After this the off-screen surface and FBO are available.
    virtual void initializeGL() = 0;
    /// @brief Called whenever the window size changes.
    /// @param width New window width.
    /// @param height New window height.
    virtual void resizeGL(int width, int height) = 0;
    /// @brief Called whenever the window needs to repaint itself. Override to draw OpenGL content.
    /// When this function is called, the context is already current and the correct framebuffer is bound.
    virtual void paintGL() = 0;
    //      /// @brief Called whenever the window needs to repaint itself. Override to draw QPainter content.
    //      /// @brief This is called AFTER paintGL()! Only needed when painting using a QPainter.
    //      virtual void paintEvent(QPainter & painter) = 0;

private:
    Q_DISABLE_COPY(QGLWindow)

    /// @brief Initialize the window.
    void initializeInternal();
    /// @brief Internal method that does the actual swap work, NOT using a mutex.
    void swapBuffersInternal();
    /// @brief Internal method that checks state and makes the context current, NOT using a mutex.
    void makeCurrentInternal();
    /// @brief Internal method to grab content of a specific framebuffer.
    QImage grabFramebufferInternal(QOpenGLFramebufferObject * fbo);

    /// @brief (Re-)allocate FBO and paint device if needed due to size changes etc.
    void recreateFBOAndPaintDevice();

    /// @brief False before the window was first exposed OR render() was called.
    std::atomic_bool m_initialized;
    /// @brief False before the overridden initializeGL() was first called.
    bool m_initializedGL = false;
    /// @brief True when currently a window update is pending.
    std::atomic_bool m_updatePending;
    /// @brief Mutex making sure not grabbing while drawing etc.
    std::mutex m_mutex;

    /// @brief OpenGL render context.
    QOpenGLContext * m_context = nullptr;
    /// @brief The OpenGL 2.1 / ES 2.0 function object that can be used the issue OpenGL commands.
    QOpenGLFunctions * m_functions = nullptr;
    /// @brief The OpenGL 3.0 function object that can be used the issue OpenGL commands.
    QOpenGLFunctions_3_0 * m_functions_3_0 = nullptr;
    /// @brief OpenGL paint device for painting with a QPainter.
    QOpenGLPaintDevice * m_paintDevice = nullptr;
    /// @brief Background FBO for off-screen rendering when the window is not exposed.
    QOpenGLFramebufferObject * m_fbo = nullptr;
    /// @brief Background FBO resolving a multi sampling frame buffer in m_fbo to a frame buffer
    /// that can be grabbed to a QImage.
    QOpenGLFramebufferObject * m_resolvedFbo = nullptr;

    /// @brief Shader used for blitting m_fbo to screen if glBlitFrameBuffer is not available.
    QOpenGLShaderProgram * m_blitShader;
};

来源:

#include "MyGLWindow.h"

#include <QtCore/QCoreApplication>
#include <QtGui/QPainter>

MyGLWindow::MyGLWindow(QScreen * targetScreen)
    : QWindow(targetScreen)
{
    //Set Qt::Widget flag to make sure the window resizes properly the first time
    //when used as a widget via MyGLWindow::createWidget()!
    setFlags(Qt::Widget);
    setSurfaceType(QSurface::OpenGLSurface);
    setFormat(QGLInfo::DefaultSurfaceFormat());
    m_initialized = false;
    m_updatePending = false;
    create();
    initializeInternal();
}

MyGLWindow::MyGLWindow(QWindow * parent)
    : QWindow(parent)
{
    //Set Qt::Widget flag to make sure the window resizes properly the first time
    //when used as a widget via MyGLWindow::createWidget()!
    setFlags(Qt::Widget);
    setSurfaceType(QSurface::OpenGLSurface);
    setFormat(QGLInfo::DefaultSurfaceFormat());
    m_initialized = false;
    m_updatePending = false;
    create();
    initializeInternal();
}

MyGLWindow::~MyGLWindow()
{
    //to delete the FBOs we first need to make the context current
    m_context->makeCurrent(this);
    //destroy framebuffer objects
    if (m_fbo)
    {
        m_fbo->release();
        delete m_fbo;
        m_fbo = nullptr;
    }
    if (m_resolvedFbo)
    {
        m_resolvedFbo->release();
        delete m_resolvedFbo;
        m_resolvedFbo = nullptr;
    }
    //destroy shader
    delete m_blitShader;
    m_blitShader = nullptr;
    //free context
    m_context->doneCurrent();
    delete m_context;
    m_context = nullptr;
    //free paint device
    delete m_paintDevice;
    m_paintDevice = nullptr;
    m_initialized = false;
    m_updatePending = false;
}

QWidget * MyGLWindow::createWidget(QWidget * parent)
{
    QWidget * container = QWidget::createWindowContainer(this, parent);
    return container;
}

QOpenGLContext * MyGLWindow::context() const
{
    return m_context;
}

QOpenGLFunctions * MyGLWindow::functions() const
{
    return m_functions;
}

GLuint MyGLWindow::framebufferObjectHandle() const
{
    return m_fbo ? m_fbo->handle() : 0;
}

const QOpenGLFramebufferObject * MyGLWindow::getFramebufferObject() const
{
    return m_fbo;
}

void MyGLWindow::bindFramebufferObject()
{
    if (m_fbo)
    {
        m_fbo->bind();
    }
    else
    {
        QOpenGLFramebufferObject::bindDefault();
    }
}

bool MyGLWindow::isValid() const
{
    return (m_initialized && m_context && m_fbo);
}

void MyGLWindow::makeCurrent()
{
    makeCurrentInternal();
}

void MyGLWindow::makeCurrentInternal()
{
    if (isValid())
    {
        m_context->makeCurrent(this);
    }
    else
    {
        throw("MyGLWindow::makeCurrent() - Window not yet properly initialized!");
    }
}

void MyGLWindow::doneCurrent()
{
    if (m_context)
    {
        m_context->doneCurrent();
    }
}

QImage MyGLWindow::grabFramebuffer()
{
    std::lock_guard<std::mutex> locker(m_mutex);
    makeCurrentInternal();
    //blit framebuffer to resolve framebuffer first if needed
    if (m_fbo->format().samples() > 0)
    {
        //check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0
        if (m_functions_3_0)
        {
            //only blit the color buffer attachment
            m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
            m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolvedFbo->handle());
            m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(),
                                                          GL_COLOR_BUFFER_BIT,
                                                          GL_NEAREST);
            m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, 0);
        }
        else
        {
            //we must unbind the FBO here, so we can use its texture and bind the default back-buffer
            m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFbo->handle());
            //now use its texture for drawing in the shader
            --> bind shader and draw textured quad here
            //bind regular FBO again
            m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
        }
        //check if OpenGL errors happened
        if (GLenum error = m_functions->glGetError() != GL_NO_ERROR)
        {
            qDebug() << "MyGLWindow::grabFramebuffer() - OpenGL error" << error;
        }
        //now grab from resolve FBO
        return grabFramebufferInternal(m_resolvedFbo);
    }
    else
    {
        //no multi-sampling. grab directly from FBO
        return grabFramebufferInternal(m_fbo);
    }
}

QImage MyGLWindow::grabFramebufferInternal(QOpenGLFramebufferObject * fbo)
{
    QImage image;
    //bind framebuffer first
    m_functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
    if (m_functions_3_0)
    {
        m_functions_3_0->glReadBuffer(GL_COLOR_ATTACHMENT0);
    }
    GLenum internalFormat = fbo->format().internalTextureFormat();
    bool hasAlpha = internalFormat == GL_RGBA || internalFormat == GL_BGRA || internalFormat == GL_RGBA8;
    if (internalFormat == GL_BGRA)
    {
        image = QImage(fbo->size(), hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
        m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
    }
    else if (internalFormat == GL_RGBA || internalFormat == GL_RGBA8)
    {
        image = QImage(fbo->size(), hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888);
        m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
    }
    else
    {
        qDebug() << "MyGLWindow::grabFramebuffer() - Unsupported framebuffer format" << internalFormat << "!";
    }
    m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
    return image.mirrored();
}

void MyGLWindow::swapBuffers()
{
    swapBuffersInternal();
    emit frameSwapped();
}

void MyGLWindow::swapBuffersInternal()
{
    if (isExposed() && isVisible())
    {
        //blit framebuffer to back buffer
        m_context->makeCurrent(this);
        //make sure all paint operation have been processed
        m_functions->glFlush();
        //check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0
        if (m_functions_3_0)
        {
            //if our framebuffer has multi-sampling, the resolve should be done automagically
            m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
            m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
            //blit all buffers including depth buffer for further rendering
            m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(),
                                                          GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
                                                          GL_NEAREST);
            m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
        }
        else
        {
            //we must unbind the FBO here, so we can use its texture and bind the default back-buffer
            m_functions->glBindFramebuffer(GL_FRAMEBUFFER, 0);
            //now use its texture for drawing in the shader
            --> bind shader and draw textured quad here
            //bind regular FBO again
            m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
        }
        //check if OpenGL errors happened
        if (GLenum error = m_functions->glGetError() != GL_NO_ERROR)
        {
            qDebug() << "MyGLWindow::swapBuffersInternal() - OpenGL error" << error;
        }
        //now swap back buffer to front buffer
        m_context->swapBuffers(this);
    }
    else
    {
        //not visible. only flush the pipeline so we can possibly grab the FBO later
        m_context->makeCurrent(this);
        m_functions->glFlush();
    }
}

void MyGLWindow::recreateFBOAndPaintDevice()
{
    const QSize deviceSize = size() * devicePixelRatio();
    if (m_context && (m_fbo == nullptr || m_fbo->size() != deviceSize))
    {
        m_context->makeCurrent(this);
        //free old FBOs
        if (m_fbo)
        {
            m_fbo->release();
            delete m_fbo;
            m_fbo = nullptr;
        }
        if (m_resolvedFbo)
        {
            m_resolvedFbo->release();
            delete m_resolvedFbo;
            m_resolvedFbo = nullptr;
        }
        //create new frame buffer
        QOpenGLFramebufferObjectFormat format = QGLInfo::DefaultFramebufferFormat();
        format.setSamples(QGLInfo::HasMultisamplingSupport(m_context) ? 4 : 0);
        m_fbo = new QOpenGLFramebufferObject(deviceSize, format);
        if (!m_fbo->isValid())
        {
            throw("MyGLWindow::recreateFbo() - Failed to create background FBO!");
        }
        //clear framebuffer
        m_fbo->bind();
        m_functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        m_fbo->release();
        //if multi sampling is requested and supported we need a resolve FBO
        if (format.samples() > 0)
        {
            //create resolve framebuffer with only a color attachment
            format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
            format.setSamples(0);
            m_resolvedFbo = new QOpenGLFramebufferObject(deviceSize, format);
            if (!m_resolvedFbo->isValid())
            {
                throw("MyGLWindow::recreateFbo() - Failed to create resolve FBO!");
            }
            //clear resolve framebuffer
            m_resolvedFbo->bind();
            m_functions->glClear(GL_COLOR_BUFFER_BIT);
            m_resolvedFbo->release();
        }
    }
    //create paint device for painting with QPainter if needed
    if (!m_paintDevice)
    {
        m_paintDevice = new QOpenGLPaintDevice;
    }
    //update paint device size if needed
    if (m_paintDevice->size() != deviceSize)
    {
        m_paintDevice->setDevicePixelRatio(devicePixelRatio());
        m_paintDevice->setSize(deviceSize);
    }
}

void MyGLWindow::initializeInternal()
{
    if (!m_initialized.exchange(true))
    {
        //create OpenGL context. we set the format requested by the user (default: QWindow::requestedFormat())
        m_context = new QOpenGLContext(this);
        m_context->setFormat(format());
        if (m_context->create())
        {
            m_context->makeCurrent(this);
            //initialize the OpenGL 2.1 / ES 2.0 functions for this object
            m_functions = m_context->functions();
            m_functions->initializeOpenGLFunctions();
            //try initializing the OpenGL 3.0 functions for this object
            m_functions_3_0 = m_context->versionFunctions<QOpenGLFunctions_3_0>();
            if (m_functions_3_0)
            {
                m_functions_3_0->initializeOpenGLFunctions();
            }
            else
            {
                //if we do not have OpenGL 3.0 functions, glBlitFrameBuffer is not available, so we must do the blit
                //using a shader and the framebuffer texture, so we need to create the shader here...
                --> allocate m_blitShader, a simple shader for drawing a textured quad
                --> build quad geometry, VBO, whatever
            }
            //now we have a context, create the FBO
            recreateFBOAndPaintDevice();
        }
        else
        {
            m_initialized = false;
            delete m_context;
            m_context = nullptr;
            throw("Failed to create OpenGL context!");
        }
    }
}

void MyGLWindow::update()
{
    //only queue an update if there's not already an update pending
    if (!m_updatePending.exchange(true))
    {
        QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
    }
}

void MyGLWindow::render()
{
    std::lock_guard<std::mutex> locker(m_mutex);
    //check if we need to initialize stuff
    initializeInternal();
    //check if we need to call the user initialization
    if (!m_initializedGL)
    {
        m_initializedGL = true;
        initializeGL();
    }
    //make context current and bind framebuffer
    makeCurrent();
    bindFramebufferObject();
    //call user paint function
    paintGL();
    doneCurrent();
    //mark that we're done with updating
    m_updatePending = false;
}

void MyGLWindow::exposeEvent(QExposeEvent * e)
{
    //call base implementation
    QWindow::exposeEvent(e);
    //render window content if window is exposed
    if (isExposed()/* && isVisible()*/)
    {
        render();
    }
}

void MyGLWindow::resizeEvent(QResizeEvent *e)
{
    //call base implementation
    QWindow::resizeEvent(e);
    m_mutex.lock();
    //make context current first
    makeCurrent();
    //update FBO and paint device
    recreateFBOAndPaintDevice();
    m_mutex.unlock();
    //call user-defined resize method
    resizeGL(e->size().width(), e->size().height());
    emit resized();
}

bool MyGLWindow::event(QEvent *event)
{
    switch (event->type())
    {
        case QEvent::UpdateLater:
            update();
            return true;
        case QEvent::UpdateRequest:
            render();
            return true;
        default:
            return QWindow::event(event);
    }
}

对于离屏渲染我们也可以使用QOffscreenSurface

这里是基于 Bim 的 MyGLWindow class 工作的(并非所有都经过测试):

Header OpenGlOffscreenSurface.h:

#ifndef OPENGLOFFSCREENSURFACE_H
#define OPENGLOFFSCREENSURFACE_H

#pragma once

#include <QtCore/QObject>
#include <QtGui/QScreen>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QPaintEvent>
#include <QtGui/QResizeEvent>
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLFunctions_3_0>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QSurfaceFormat>
#include <QtWidgets/QWidget>
#include <QOpenGLShaderProgram>

#include <atomic>
#include <mutex>

class OpenGlOffscreenSurface
    : public QOffscreenSurface
{
    Q_OBJECT

public:
    /// @brief Constructor. Creates a render window.
    /// @param targetScreen Target screen.
    /// @param size Initial size of a surface buffer.
    /// this is because before the FBO and off-screen surface haven't been created.
    /// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen
    /// surface.
    explicit OpenGlOffscreenSurface(
            QScreen* targetScreen = nullptr,
            const QSize& size = QSize (1, 1));

    /// @brief Destructor.
    virtual ~OpenGlOffscreenSurface();

    /// @brief Check if the window is initialized and can be used for rendering.
    /// @return Returns true if context, surface and FBO have been set up to start rendering.
    bool isValid() const;

    /// @brief Return the context used in this window.
    /// @return The context used in this window or nullptr if it hasn't been created yet.
    QOpenGLContext* context() const;

    /// @brief Return the OpenGL function object that can be used the issue OpenGL commands.
    /// @return The functions for the context or nullptr if it the context hasn't been created yet.
    QOpenGLFunctions* functions() const;

    /// @brief Return the OpenGL off-screen frame buffer object identifier.
    /// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created
    /// yet.
    /// @note This changes on every resize!
    GLuint framebufferObjectHandle() const;

    /// @brief Return the OpenGL off-screen frame buffer object.
    /// @return The OpenGL off-screen frame buffer object or nullptr if no FBO has been created yet.
    /// @note This changes on every resize!
    const QOpenGLFramebufferObject* getFramebufferObject() const;

    /// @brief Return the QPaintDevice for paint into it.
    QPaintDevice* getPaintDevice() const;

    /// @brief Return the OpenGL off-screen frame buffer object identifier.
    /// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created
    /// yet.
    void bindFramebufferObject();

    /// @brief Return the current contents of the FBO.
    /// @return FBO content as 32bit QImage. You might need to swap RGBA to BGRA or vice-versa.
    QImage grabFramebuffer();

    /// @brief Makes the OpenGL context current for rendering.
    /// @note Make sure to bindFramebufferObject() if you want to render to this widgets FBO.
    void makeCurrent();

    /// @brief Release the OpenGL context.
    void doneCurrent();

    /// @brief Copy content of framebuffer to back buffer and swap buffers if the surface is
    /// double-buffered.
    /// If the surface is not double-buffered, the frame buffer content is blitted to the front
    /// buffer.
    /// If the window is not exposed, only the OpenGL pipeline is glFlush()ed so the framebuffer can
    /// be read back.
    void swapBuffers();

    /// @brief Use bufferSize() instead size() for get a size of a surface buffer. We can't override size() as it is not virtual.
    QSize bufferSize() const;

    /// @brief Resize surface buffer to newSize.
    void resize(const QSize& newSize);

    /// @brief Resize surface buffer to size with width w and height h.
    /// @param w Width.
    /// @param h Height.
    void resize(int w, int h);

public slots:
    /// @brief Lazy update routine like QWidget::update().
    void update();

    /// @brief Immediately render the widget contents to framebuffer.
    void render();

signals:
    /// @brief Emitted when swapBuffers() was called and bufferswapping is done.
    void frameSwapped();

    /// @brief Emitted after a resizeEvent().
    void resized();

protected:
    virtual void exposeEvent(QExposeEvent* e);
    virtual void resizeEvent(QResizeEvent* e);
    virtual bool event(QEvent* e) override;

//    virtual int metric(QPaintDevice::PaintDeviceMetric metric) const override;

    /// @brief Called exactly once when the window is first exposed OR render() is called when the
    /// widget is invisible.
    /// @note After this the off-screen surface and FBO are available.
    virtual void initializeGL() = 0;

    /// @brief Called whenever the window size changes.
    /// @param width New window width.
    /// @param height New window height.
    virtual void resizeGL(
            int width,
            int height) = 0;

    /// @brief Called whenever the window needs to repaint itself. Override to draw OpenGL content.
    /// When this function is called, the context is already current and the correct framebuffer is
    /// bound.
    virtual void paintGL() = 0;

    //      /// @brief Called whenever the window needs to repaint itself. Override to draw QPainter
    // content.
    //      /// @brief This is called AFTER paintGL()! Only needed when painting using a QPainter.
    //      virtual void paintEvent(QPainter & painter) = 0;

private:
    Q_DISABLE_COPY(OpenGlOffscreenSurface)
    /// @brief Initialize the window.
    void initializeInternal();

    /// @brief Internal method that does the actual swap work, NOT using a mutex.
    void swapBuffersInternal();

    /// @brief Internal method that checks state and makes the context current, NOT using a mutex.
    void makeCurrentInternal();

    /// @brief Internal method to grab content of a specific framebuffer.
    QImage grabFramebufferInternal(QOpenGLFramebufferObject* fbo);

    /// @brief (Re-)allocate FBO and paint device if needed due to size changes etc.
    void recreateFBOAndPaintDevice();

    /// @brief False before the window was first exposed OR render() was called.
    std::atomic_bool m_initialized;
    /// @brief False before the overridden initializeGL() was first called.
    bool m_initializedGL = false;
    /// @brief True when currently a window update is pending.
    std::atomic_bool m_updatePending;
    /// @brief Mutex making sure not grabbing while drawing etc.
    std::mutex m_mutex;

    /// @brief OpenGL render context.
    QOpenGLContext* m_context = nullptr;
    /// @brief The OpenGL 2.1 / ES 2.0 function object that can be used the issue OpenGL commands.
    QOpenGLFunctions* m_functions = nullptr;
    /// @brief The OpenGL 3.0 function object that can be used the issue OpenGL commands.
    QOpenGLFunctions_3_0* m_functions_3_0 = nullptr;
    /// @brief OpenGL paint device for painting with a QPainter.
    QOpenGLPaintDevice* m_paintDevice = nullptr;
    /// @brief Background FBO for off-screen rendering when the window is not exposed.
    QOpenGLFramebufferObject* m_fbo = nullptr;
    /// @brief Background FBO resolving a multi sampling frame buffer in m_fbo to a frame buffer
    /// that can be grabbed to a QImage.
    QOpenGLFramebufferObject* m_resolvedFbo = nullptr;

    /// @brief Shader used for blitting m_fbo to screen if glBlitFrameBuffer is not available.
    QOpenGLShaderProgram* m_blitShader;

    QSize m_size;
};

#endif  // OPENGLOFFSCREENSURFACE_H

来源OpenGlOffscreenSurface.cpp:

#include "OpenGlOffscreenSurface.h"

#include <QtCore/QCoreApplication>
#include <QtGui/QPainter>

OpenGlOffscreenSurface::OpenGlOffscreenSurface(
        QScreen*     targetScreen,
        const QSize& size)
    : QOffscreenSurface(targetScreen)
    , m_size(size)
{
    setFormat(QSurfaceFormat::defaultFormat());
    m_initialized = false;
    m_updatePending = false;
    create();  // Some platforms require this function to be called on the main (GUI) thread
    initializeInternal();
}


OpenGlOffscreenSurface::~OpenGlOffscreenSurface()
{
    // to delete the FBOs we first need to make the context current
    m_context->makeCurrent(this);
    // destroy framebuffer objects
    if (m_fbo) {
        m_fbo->release();
        delete m_fbo;
        m_fbo = nullptr;
    }
    if (m_resolvedFbo) {
        m_resolvedFbo->release();
        delete m_resolvedFbo;
        m_resolvedFbo = nullptr;
    }
    // destroy shader
    delete m_blitShader;
    m_blitShader = nullptr;
    // free context
    m_context->doneCurrent();
    delete m_context;
    m_context = nullptr;
    // free paint device
    delete m_paintDevice;
    m_paintDevice = nullptr;
    m_initialized = false;
    m_updatePending = false;
    destroy();
}


QOpenGLContext* OpenGlOffscreenSurface::context() const
{
    return (m_context);
}


QOpenGLFunctions* OpenGlOffscreenSurface::functions() const
{
    return (m_functions);
}


GLuint OpenGlOffscreenSurface::framebufferObjectHandle() const
{
    return (m_fbo ? m_fbo->handle() : 0);
}


const QOpenGLFramebufferObject* OpenGlOffscreenSurface::getFramebufferObject() const
{
    return (m_fbo);
}


QPaintDevice* OpenGlOffscreenSurface::getPaintDevice() const
{
    return (m_paintDevice);
}


void OpenGlOffscreenSurface::bindFramebufferObject()
{
    if (m_fbo) {
        m_fbo->bind();
    } else {
        QOpenGLFramebufferObject::bindDefault();
    }
}


bool OpenGlOffscreenSurface::isValid() const
{
    return (m_initialized && m_context && m_fbo);
}


void OpenGlOffscreenSurface::makeCurrent()
{
    makeCurrentInternal();
}


void OpenGlOffscreenSurface::makeCurrentInternal()
{
    if (isValid()) {
        m_context->makeCurrent(this);
    } else {
        throw ("OpenGlOffscreenSurface::makeCurrent() - Window not yet properly initialized!");
    }
}


void OpenGlOffscreenSurface::doneCurrent()
{
    if (m_context) {
        m_context->doneCurrent();
    }
}


QImage OpenGlOffscreenSurface::grabFramebuffer()
{
    std::lock_guard <std::mutex> locker(m_mutex);
    makeCurrentInternal();
    // blit framebuffer to resolve framebuffer first if needed
    if (m_fbo->format().samples() > 0) {
        // check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not
        // OpenGL ES 2.0
        if (m_functions_3_0) {
            // only blit the color buffer attachment
            m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
            m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolvedFbo->handle());
            m_functions_3_0->glBlitFramebuffer(0, 0, bufferSize().width(),
                    bufferSize().height(), 0, 0, bufferSize().width(),
                    bufferSize().height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
            m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, 0);
        } else {
            // we must unbind the FBO here, so we can use its texture and bind the default
            // back-buffer
            m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFbo->handle());
            // now use its texture for drawing in the shader
            // --> bind shader and draw textured quad here
            // bind regular FBO again
            m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
        }
        // check if OpenGL errors happened
        if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) {
            qDebug() << "OpenGlOffscreenSurface::grabFramebuffer() - OpenGL error" << error;
        }

        // now grab from resolve FBO
        return (grabFramebufferInternal(m_resolvedFbo));
    } else {
        // no multi-sampling. grab directly from FBO
        return (grabFramebufferInternal(m_fbo));
    }
}  // OpenGlOffscreenSurface::grabFramebuffer


QImage OpenGlOffscreenSurface::grabFramebufferInternal(QOpenGLFramebufferObject* fbo)
{
    QImage image;
    // bind framebuffer first
    m_functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
    if (m_functions_3_0) {
        m_functions_3_0->glReadBuffer(GL_COLOR_ATTACHMENT0);
    }
    GLenum internalFormat = fbo->format().internalTextureFormat();
    bool hasAlpha = internalFormat == GL_RGBA || internalFormat == GL_BGRA
                    || internalFormat == GL_RGBA8;
    if (internalFormat == GL_BGRA) {
        image = QImage(fbo->size(), hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
        m_functions->glReadPixels(0, 0, fbo->size().width(),
                fbo->size().height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
    } else if ((internalFormat == GL_RGBA) || (internalFormat == GL_RGBA8)) {
        image = QImage(fbo->size(), hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888);
        m_functions->glReadPixels(0, 0, fbo->size().width(),
                fbo->size().height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
    } else {
        qDebug() << "OpenGlOffscreenSurface::grabFramebuffer() - Unsupported framebuffer format"
                 << internalFormat << "!";
    }
    m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());

    return (image.mirrored());
}  // OpenGlOffscreenSurface::grabFramebufferInternal


void OpenGlOffscreenSurface::swapBuffers()
{
    swapBuffersInternal();
    emit frameSwapped();
}


void OpenGlOffscreenSurface::swapBuffersInternal()
{
    // blit framebuffer to back buffer
    m_context->makeCurrent(this);
    // make sure all paint operation have been processed
    m_functions->glFlush();
    // check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not
    // OpenGL ES 2.0
    if (m_functions_3_0) {
        // if our framebuffer has multi-sampling, the resolve should be done automagically
        m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
        m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
        // blit all buffers including depth buffer for further rendering
        m_functions_3_0->glBlitFramebuffer(0, 0, bufferSize().width(),
                bufferSize().height(), 0, 0, bufferSize().width(),
                bufferSize().height(), GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
                GL_NEAREST);
        m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
    } else {
        // we must unbind the FBO here, so we can use its texture and bind the default back-buffer
        m_functions->glBindFramebuffer(GL_FRAMEBUFFER, 0);
        // now use its texture for drawing in the shader
        // --> bind shader and draw textured quad here
        // bind regular FBO again
        m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
    }
    // check if OpenGL errors happened
    if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) {
        qDebug() << "OpenGlOffscreenSurface::swapBuffersInternal() - OpenGL error" << error;
    }
    // now swap back buffer to front buffer
    m_context->swapBuffers(this);
}  // OpenGlOffscreenSurface::swapBuffersInternal


void OpenGlOffscreenSurface::recreateFBOAndPaintDevice()
{
    if (m_context && ((m_fbo == nullptr) || (m_fbo->size() != bufferSize()))) {
        m_context->makeCurrent(this);
        // free old FBOs
        if (m_fbo) {
            m_fbo->release();
            delete m_fbo;
            m_fbo = nullptr;
        }
        if (m_resolvedFbo) {
            m_resolvedFbo->release();
            delete m_resolvedFbo;
            m_resolvedFbo = nullptr;
        }
        // create new frame buffer
//        QOpenGLFramebufferObjectFormat format = QGLInfo::DefaultFramebufferFormat();
//        format.setSamples(QGLInfo::HasMultisamplingSupport(m_context) ? 4 : 0);
        QOpenGLFramebufferObjectFormat format;
        format.setSamples(0);

        m_fbo = new QOpenGLFramebufferObject(bufferSize(), format);
        if (!m_fbo->isValid()) {
            throw ("OpenGlOffscreenSurface::recreateFbo() - Failed to create background FBO!");
        }
        // clear framebuffer
        m_fbo->bind();
        m_functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        m_fbo->release();
        // if multi sampling is requested and supported we need a resolve FBO
        if (format.samples() > 0) {
            // create resolve framebuffer with only a color attachment
            format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
            format.setSamples(0);
            m_resolvedFbo = new QOpenGLFramebufferObject(bufferSize(), format);
            if (!m_resolvedFbo->isValid()) {
                throw ("OpenGlOffscreenSurface::recreateFbo() - Failed to create resolve FBO!");
            }
            // clear resolve framebuffer
            m_resolvedFbo->bind();
            m_functions->glClear(GL_COLOR_BUFFER_BIT);
            m_resolvedFbo->release();
        }
    }
    // create paint device for painting with QPainter if needed
    if (!m_paintDevice) {
        m_paintDevice = new QOpenGLPaintDevice;
    }
    // update paint device size if needed
    if (m_paintDevice->size() != bufferSize()) {
        m_paintDevice->setSize(bufferSize());
    }
}  // OpenGlOffscreenSurface::recreateFBOAndPaintDevice


void OpenGlOffscreenSurface::initializeInternal()
{
    if (!m_initialized.exchange(true)) {
        // create OpenGL context. we set the format requested by the user (default:
        // QWindow::requestedFormat())
        m_context = new QOpenGLContext(this);
        m_context->setFormat(format());
        if (m_context->create()) {
            m_context->makeCurrent(this);
            // initialize the OpenGL 2.1 / ES 2.0 functions for this object
            m_functions = m_context->functions();
            m_functions->initializeOpenGLFunctions();
            // try initializing the OpenGL 3.0 functions for this object
            m_functions_3_0 = m_context->versionFunctions <QOpenGLFunctions_3_0>();
            if (m_functions_3_0) {
                m_functions_3_0->initializeOpenGLFunctions();
            } else {
                // if we do not have OpenGL 3.0 functions, glBlitFrameBuffer is not available, so we
                // must do the blit
                // using a shader and the framebuffer texture, so we need to create the shader
                // here...
                // --> allocate m_blitShader, a simple shader for drawing a textured quad
                // --> build quad geometry, VBO, whatever
            }
            // now we have a context, create the FBO
            recreateFBOAndPaintDevice();
        } else {
            m_initialized = false;
            delete m_context;
            m_context = nullptr;
            throw ("Failed to create OpenGL context!");
        }
    }
}  // OpenGlOffscreenSurface::initializeInternal


void OpenGlOffscreenSurface::update()
{
    // only queue an update if there's not already an update pending
    if (!m_updatePending.exchange(true)) {
        QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
    }
}


void OpenGlOffscreenSurface::render()
{
    std::lock_guard <std::mutex> locker(m_mutex);
    // check if we need to initialize stuff
    initializeInternal();
    // check if we need to call the user initialization
//    makeCurrent(); // TODO: may be makeCurrent() must be here, as noted for QOpenGLWidget.initializeGL()
    if (!m_initializedGL) {
        m_initializedGL = true;
        initializeGL();
    }
    // make context current and bind framebuffer
    makeCurrent();
    bindFramebufferObject();
    // call user paint function
    paintGL();
    doneCurrent();
    // mark that we're done with updating
    m_updatePending = false;
}  // OpenGlOffscreenSurface::render


void OpenGlOffscreenSurface::exposeEvent(QExposeEvent* e)
{
    // render window content if window is exposed
    render();
}  // OpenGlOffscreenSurface::exposeEvent


void OpenGlOffscreenSurface::resizeEvent(QResizeEvent* e)
{
    // call base implementation
    resize(e->size());
    emit resized();
}


void OpenGlOffscreenSurface::resize(const QSize& newSize)
{
    m_mutex.lock();
    // make context current first
    makeCurrent();

    m_size = QSize(newSize);

    // update FBO and paint device
    recreateFBOAndPaintDevice();
    m_mutex.unlock();
    // call user-defined resize method
    resizeGL(bufferSize().width(), bufferSize().height());
}  // OpenGlOffscreenSurface::resize


void OpenGlOffscreenSurface::resize(
        int w,
        int h)
{
    resize(QSize(w, h));
}


bool OpenGlOffscreenSurface::event(QEvent* event)
{
    switch (event->type()) {
        case QEvent::UpdateLater:
            update();
            return (true);

        case QEvent::UpdateRequest:
            render();
            return (true);

        default:
            return (false);
    }  // switch
}  // OpenGlOffscreenSurface::event


QSize OpenGlOffscreenSurface::bufferSize() const
{
    return (m_size);
}

使用OpenGlOffscreenSurface

Header ExamplePaintSurface.h:

#ifndef EXAMPLEPAINTSURFACE_H
#define EXAMPLEPAINTSURFACE_H

#include "OpenGlOffscreenSurface.h"

class ExamplePaintSurface
    : public OpenGlOffscreenSurface
{
public:
    explicit ExamplePaintSurface(
            QScreen* targetScreen = nullptr,
            const QSize& size = QSize (1, 1));

    virtual ~ExamplePaintSurface() override;

protected:
    virtual void initializeGL() override;

    virtual void resizeGL(
            int width,
            int height) override;

    virtual void paintGL() override;
};


#endif  // EXAMPLEPAINTSURFACE_H

来源ExamplePaintSurface.cpp:

#include "ExamplePaintSurface.h"

#include <QPainter>

ExamplePaintSurface::ExamplePaintSurface(
        QScreen*     targetScreen,
        const QSize& size)
    : OpenGlOffscreenSurface(targetScreen, size) {}


ExamplePaintSurface::~ExamplePaintSurface() {}


void ExamplePaintSurface::initializeGL() {}


void ExamplePaintSurface::resizeGL(int width, int height) {}


void ExamplePaintSurface::paintGL()
{
    QPainter painter(getPaintDevice());
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);

    painter.drawText(20, 40, "Test"); // <-- drawing here

    painter.end();
}

来源main.cpp:

#include <QApplication>

#include "ExamplePaintSurface.h"


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

    ExamplePaintSurface paintSurface;
    paintSurface.resize(300, 200);
    paintSurface.render();
    QImage image = paintSurface.grabFramebuffer();
    image.save(QString("image.png"));

    return a.exec();
}

带有 QOffscreenSurface 的部分示例来自 此处:

#include <QGuiApplication>
#include <QSurfaceFormat>
#include <QOpenGlContext>
#include <QOffscreenSurface>
#include <QOpenGLFunctions_4_3_Core>
#include <QDebug>

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

   QSurfaceFormat surfaceFormat;
   surfaceFormat.setMajorVersion(4);
   surfaceFormat.setMinorVersion(3);

   QOpenGLContext openGLContext;
   openGLContext.setFormat(surfaceFormat);
   openGLContext.create();
   if(!openGLContext.isValid()) return -1;

   QOffscreenSurface surface;
   surface.setFormat(surfaceFormat);
   surface.create();
   if(!surface.isValid()) return -2;

   openGLContext.makeCurrent(&surface);

   QOpenGLFunctions_4_3_Core f;
   if(!f.initializeOpenGLFunctions()) return -3;

   qDebug() << QString::fromLatin1((const char*)f.glGetString(GL_VERSION));

   return 0;
}

here 是一个关于它的答案:

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.