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
Post a Comment