【问题标题】:Easiest way for offscreen rendering with QOpenGLWidget使用 QOpenGLWidget 进行屏幕外渲染的最简单方法
【发布时间】:2015-07-09 16:53:10
【问题描述】:

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

【问题讨论】:

    标签: qt opengl framebuffer off-screen


    【解决方案1】:

    更新 2:QOpenGLWidget 中的 corresponding bug 似乎在 Qt 5.10 中已修复,所以我建议再次使用该类。虽然您可能希望等待 this bug 也得到修复...
    更新 1:添加 3,使用自定义 QWindow 派生类的最佳解决方案

    1 - QOpenGLWidget
    如果隐藏的 QOpenGLWidget 确实分配了帧缓冲区(不确定是否发生这种情况),仍然无法手动绑定它,因为您无法获取缓冲区 id。此外,没有调用必要的函数 initializeGL()、resizeGL() 和paintGL,并且没有函数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,将多重采样帧缓冲区转换为非多重采样帧缓冲区。 类接口可以类似于QOpenGLWidget(虚拟initializeGL()、resizeGL()、paintGL(),供用户使用)。重新实现 exposeEvent()、resizeEvent() 和 event()(您可能也需要重新实现 metric())。
    一个半完整的实现:

    标题:

    #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);
        }
    }
    

    【讨论】:

    • 我知道这是一篇旧文章,但我正在尝试了解它是如何工作的,据我从发布的代码中可以看出,方法 drawOffscreen 不包含在类原型中,实际上从未包含叫?你能帮忙吗?
    • The "bug" is fixed in Qt 5.10 now,所以我会走 QOpenGLWidget 路线并忽略 #1 和 #2。如果您想滚动自己的窗口/小部件,请使用 #3 或查看 Nikita Feodonits 的答案。
    • 问题: 1. 如果我使用#1,我是否需要运行 QApplication 或 QGuiApplication 或者如果没有这些,它是否真的会按需绘制? 2. 如果我只使用原生 gl 函数,我必须在 paintGL 中使用 QPainter 吗?
    • @Geronimo:首先:使用已修复错误的 Qt 版本!如果你不能: 1. 很可能。 2. 不,你为什么要这样做。
    • 如果想用 PySide2 选择选项 2,不要尝试实现它。 PySide2 缺少 QOpenGLPaintDevice:bugreports.qt.io/browse/PYSIDE-1090 只有 PySide6 暴露了它。
    【解决方案2】:

    对于屏幕外渲染,我们也可以使用QOffscreenSurface

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

    标头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

    标头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 的部分示例来自here

    #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 对此表示怀疑:

    对于离屏渲染,尝试渲染成 QOpenGLFramebufferObject,可以是converted into a QImage,又可以很容易地保存到磁盘。

    但是,您仍然需要一个要渲染的表面(根据需要 QOpenGLContext::makeCurrent()),所以你唯一的选择是 确实使用QWindowQGLWidget 来获得这样的表面。

    在 Qt 5.1 中,将会有一个新的类叫做 QOffscreenSurface, 这可能最适合您的用例。你会用 QOffscreenSurface 为您的OpenGL context 获取表面,然后使用 QOpenGLFramebufferObject,然后拨打 QOpenGLFramebufferObject::toImage() 访问 像素。

    【讨论】:

    • 非常感谢您的评论!很棒的资源,但是我确实有一个奇怪的问题...我使用过这个类,但是我没有创建新的上下文,而是从 QOpenGLWindow 传递了现有的上下文,它渲染得很好,但是我注意到在捕获缓冲区时,它捕获了旧的 QOpenGLWindow缓冲第一。我必须捕获缓冲区 2x 才能获得第二个 - offscreensurface 被绘制到...任何想法我错过了什么信号/轻推?我尝试在drawwing之前/之后/中间调用swapBuffer(),但到目前为止没有运气。感觉有一些信号延迟...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    • 2012-12-11
    相关资源
    最近更新 更多