qt - Easiest way for offscreen rendering with QOpenGLWidget -


i have hidden qopenglwidget (qt 5.4.2, not qglwidget) , want continually grab() or grabframebuffer() content (and write disk). widget renders fine when visible, not when hidden. if show() followed hide() call works. seems strange because qopenglwidget internally render framebuffer according docs. easiest way achieve (if possible without creating framebuffer)?
bonus points being able capture offscreen qgraphicsview using qopenglwidget viewport custom opengl-painted qgraphicsitems in it...

update 2: corresponding bug in qopenglwidget seems fixed in qt 5.10, suggest use class again. although might want wait this bug fixed...
update 1: added 3, best solution using custom qwindow-derived class

1 - qopenglwidget
if hidden qopenglwidget allocate framebuffer (not sure if happens), there still no way bind manually, because can not buffer id. additionally none of necessary functions initializegl(), resizegl() , paintgl called , none of functions grab(), grabframebuffer , render() working correctly. here (imo) workaround draw widget offscreen. call paintgl directly after setting necessary stuff:

class glwidget: public qopenglwidget { public:     glwidget(qwidget * parent = nullptr); private:     bool m_isinitialized = false;     qopenglframebufferobject m_fbo = nullptr; }; 

void glwidget::drawoffscreen() {         //the context should valid. make sure current painting     makecurrent();     if (!m_isinitialized)     {         initializegl();         resizegl(width(), height());     }     if (!m_fbo || m_fbo->width() != width() || m_fbo->height() != height())     {         //allocate additional? fbo rendering or resize if widget size changed         delete m_fbo;         qopenglframebufferobjectformat format;         format.setattachment(qopenglframebufferobject::combineddepthstencil);         m_fbo = new qopenglframebufferobject(width(), height(), format);         resizegl(width(), height());     }      //#1 not work: bind fbo , render() widget     m_fbo->bind();     qopenglpaintdevice fbopaintdev(width(), height());     qpainter painter(&fbopaintdev);     painter.setrenderhints(qpainter::antialiasing | qpainter::textantialiasing);     render(&painter);     painter.end();     //you grab content of framebuffer we've rendered     qimage image1 = m_fbo->toimage();     image1.save(qstring("fb1.png"));     m_fbo->release();     //#1 --------------------------------------------------------------      //#2 works: bind fbo , render stuff paintgl() call     m_fbo->bind();     paintgl();     //you grab content of framebuffer we've rendered     qimage image2 = m_fbo->toimage();     image2.save(qstring("fb2.png"));     m_fbo->release();     //#2 --------------------------------------------------------------      //bind default framebuffer again. not sure if necessary     //and isn't supposed use defaultframebuffer()...     m_fbo->binddefault();     donecurrent(); }  void glwidget::paintgl() {     //when doing mixed qpainter/opengl rendering make sure use qopenglpaintdevice, otherwise opengl content visible!     //i'm not sure why, because according docs (http://doc.qt.io/qt-5/topics-graphics.html) supposed same...     qopenglpaintdevice fbopaintdev(width(), height());     qpainter painter(&fbopaintdev);     painter.setrenderhints(qpainter::antialiasing | qpainter::textantialiasing);     //this you'd use (and work) if widget 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 qpainter     painter.drawtext(20, 40, "foo");     ...     painter.end(); } 

2 - qgraphicsview qopenglwidget viewport
here render() works expected when provide qopenglpaintdevice:

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 scrollbars, no opengl content     pixmap1.save("bla1.png");     qpixmap pixmap2 = hiddenglwidget->grab(); //produces empty image     pixmap2.save("bla2.png");     //try grabbing 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 , captures mixed opengl , 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 - how render , grab image hidden qopenglwidget
better overall solution use custom qwindow qsurface::openglsurface type. create qopenglcontext, background qopenglframebufferobject draw to, , qopenglshaderprogram blit framebuffer backbuffer. if want multisampling, might need resolve qopenglframebufferobject too, convert multisampled framebuffer non-multisampled one. class interface can similar qopenglwidget (virtual initializegl(), resizegl(), paintgl() users). reimplement exposeevent(), resizeevent() , event() (you might need reimplement metric() too).
semi-complete implementation:

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 render window.     /// @param targetscreen target screen.     /// because before fbo , off-screen surface haven't been created.     /// default uses qwindow::requestedformat() opengl context , off-screen surface.     explicit myglwindow(qscreen * targetscreen = nullptr);      /// @brief constructor. creates render window.     /// @param parent parent window.     /// because before fbo , off-screen surface haven't been created.     /// default uses qwindow::requestedformat() opengl context , off-screen surface.     explicit myglwindow(qwindow * parent);      /// @brief destructor.     virtual ~myglwindow();      /// @brief create container widget window.     /// @param parent parent widget.     /// @return returns container widget window.     qwidget * createwidget(qwidget * parent = nullptr);      /// @brief check if window initialized , can used rendering.     /// @return returns true if context, surface , fbo have been set start rendering.     bool isvalid() const;      /// @brief return context used in window.     /// @return context used in window or nullptr if hasn't been created yet.     qopenglcontext * context() const;      /// @brief return opengl function object can used issue opengl commands.     /// @return functions context or nullptr if context hasn't been created yet.     qopenglfunctions * functions() const;      /// @brief return opengl off-screen frame buffer object identifier.     /// @return opengl off-screen frame buffer object identifier or 0 if no fbo has been created yet.     /// @note changes on every resize!     gluint framebufferobjecthandle() const;      /// @brief return opengl off-screen frame buffer object.     /// @return opengl off-screen frame buffer object or nullptr if no fbo has been created yet.     /// @note changes on every resize!     const qopenglframebufferobject * getframebufferobject() const;      /// @brief return opengl off-screen frame buffer object identifier.     /// @return opengl off-screen frame buffer object identifier or 0 if no fbo has been created yet.     void bindframebufferobject();      /// @brief return current contents of fbo.     /// @return fbo content 32bit qimage. might need swap rgba bgra or vice-versa.     qimage grabframebuffer();      /// @brief makes opengl context current rendering.     /// @note make sure bindframebufferobject() if want render widgets fbo.     void makecurrent();      /// @brief release opengl context.     void donecurrent();      /// @brief copy content of framebuffer buffer , swap buffers if surface double-buffered.     /// if surface not double-buffered, frame buffer content blitted front buffer.     /// if window not exposed, opengl pipeline glflush()ed framebuffer can read back.     void swapbuffers();      public slots:     /// @brief lazy update routine qwidget::update().     void update();     /// @brief render widget contents framebuffer.     void render();  signals:     /// @brief emitted when swapbuffers() called , bufferswapping done.     void frameswapped();     /// @brief emitted after 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 once when window first exposed or render() called when widget invisible.     /// @note after off-screen surface , fbo available.     virtual void initializegl() = 0;     /// @brief called whenever window size changes.     /// @param width new window width.     /// @param height new window height.     virtual void resizegl(int width, int height) = 0;     /// @brief called whenever window needs repaint itself. override draw opengl content.     /// when function called, context current , correct framebuffer bound.     virtual void paintgl() = 0;     //      /// @brief called whenever window needs repaint itself. override draw qpainter content.     //      /// @brief called after paintgl()! needed when painting using qpainter.     //      virtual void paintevent(qpainter & painter) = 0;  private:     q_disable_copy(qglwindow)      /// @brief initialize window.     void initializeinternal();     /// @brief internal method actual swap work, not using mutex.     void swapbuffersinternal();     /// @brief internal method checks state , makes context current, not using mutex.     void makecurrentinternal();     /// @brief internal method grab content of specific framebuffer.     qimage grabframebufferinternal(qopenglframebufferobject * fbo);      /// @brief (re-)allocate fbo , paint device if needed due size changes etc.     void recreatefboandpaintdevice();      /// @brief false before window first exposed or render() called.     std::atomic_bool m_initialized;     /// @brief false before overridden initializegl() first called.     bool m_initializedgl = false;     /// @brief true when window update 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 opengl 2.1 / es 2.0 function object can used issue opengl commands.     qopenglfunctions * m_functions = nullptr;     /// @brief opengl 3.0 function object can used issue opengl commands.     qopenglfunctions_3_0 * m_functions_3_0 = nullptr;     /// @brief opengl paint device painting qpainter.     qopenglpaintdevice * m_paintdevice = nullptr;     /// @brief background fbo off-screen rendering when window not exposed.     qopenglframebufferobject * m_fbo = nullptr;     /// @brief background fbo resolving multi sampling frame buffer in m_fbo frame buffer     /// can grabbed qimage.     qopenglframebufferobject * m_resolvedfbo = nullptr;      /// @brief shader used blitting m_fbo screen if glblitframebuffer not available.     qopenglshaderprogram * m_blitshader; }; 

source:

#include "myglwindow.h"  #include <qtcore/qcoreapplication> #include <qtgui/qpainter>  myglwindow::myglwindow(qscreen * targetscreen)     : qwindow(targetscreen) {     //set qt::widget flag make sure window resizes first time     //when used 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 make sure window resizes first time     //when used 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 fbos first need make 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 initialized!");     } }  void myglwindow::donecurrent() {     if (m_context)     {         m_context->donecurrent();     } }  qimage myglwindow::grabframebuffer() {     std::lock_guard<std::mutex> locker(m_mutex);     makecurrentinternal();     //blit framebuffer resolve framebuffer first if needed     if (m_fbo->format().samples() > 0)     {         //check if have glframebufferblit support. true desktop opengl 3.0+, not opengl es 2.0         if (m_functions_3_0)         {             //only blit 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 fbo here, can use texture , bind default back-buffer             m_functions->glbindframebuffer(gl_framebuffer, m_resolvedfbo->handle());             //now use texture drawing in shader             --> bind shader , 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 resolve fbo         return grabframebufferinternal(m_resolvedfbo);     }     else     {         //no multi-sampling. grab directly 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 buffer         m_context->makecurrent(this);         //make sure paint operation have been processed         m_functions->glflush();         //check if have glframebufferblit support. true desktop opengl 3.0+, not opengl es 2.0         if (m_functions_3_0)         {             //if our framebuffer has multi-sampling, resolve should done automagically             m_functions_3_0->glbindframebuffer(gl_read_framebuffer, m_fbo->handle());             m_functions_3_0->glbindframebuffer(gl_draw_framebuffer, 0);             //blit buffers including depth buffer 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 fbo here, can use texture , bind default back-buffer             m_functions->glbindframebuffer(gl_framebuffer, 0);             //now use texture drawing in shader             --> bind shader , 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 buffer front buffer         m_context->swapbuffers(this);     }     else     {         //not visible. flush pipeline can possibly grab 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 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 requested , supported need resolve fbo         if (format.samples() > 0)         {             //create resolve framebuffer color attachment             format.setattachment(qopenglframebufferobject::noattachment);             format.setsamples(0);             m_resolvedfbo = new qopenglframebufferobject(devicesize, format);             if (!m_resolvedfbo->isvalid())             {                 throw("myglwindow::recreatefbo() - failed create resolve fbo!");             }             //clear resolve framebuffer             m_resolvedfbo->bind();             m_functions->glclear(gl_color_buffer_bit);             m_resolvedfbo->release();         }     }     //create paint device painting 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. set format requested user (default: qwindow::requestedformat())         m_context = new qopenglcontext(this);         m_context->setformat(format());         if (m_context->create())         {             m_context->makecurrent(this);             //initialize opengl 2.1 / es 2.0 functions object             m_functions = m_context->functions();             m_functions->initializeopenglfunctions();             //try initializing opengl 3.0 functions object             m_functions_3_0 = m_context->versionfunctions<qopenglfunctions_3_0>();             if (m_functions_3_0)             {                 m_functions_3_0->initializeopenglfunctions();             }             else             {                 //if not have opengl 3.0 functions, glblitframebuffer not available, must blit                 //using shader , framebuffer texture, need create shader here...                 --> allocate m_blitshader, simple shader drawing textured quad                 --> build quad geometry, vbo, whatever             }             //now have context, create fbo             recreatefboandpaintdevice();         }         else         {             m_initialized = false;             delete m_context;             m_context = nullptr;             throw("failed create opengl context!");         }     } }  void myglwindow::update() {     //only queue update if there's not 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 need initialize stuff     initializeinternal();     //check if need call user initialization     if (!m_initializedgl)     {         m_initializedgl = true;         initializegl();     }     //make context current , bind framebuffer     makecurrent();     bindframebufferobject();     //call user paint function     paintgl();     donecurrent();     //mark we're done updating     m_updatepending = false; }  void myglwindow::exposeevent(qexposeevent * e) {     //call base implementation     qwindow::exposeevent(e);     //render window content if window 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 , 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);     } } 

Comments