Easiest way for offscreen rendering with QOpenGLWidget
我有一个隐藏的QOpenGLWidget(Qt 5.4.2,不是QGLWidget),并且我想基本上连续地进行抓()或抓帧缓冲区()以获取其内容(并将其写入磁盘)。 该小部件在可见时呈现良好,但在隐藏时则不呈现。 如果我先执行show(),然后执行hide()调用,那么它将起作用。 这似乎很奇怪,因为根据文档,QOpenGLWidget在内部确实已渲染到帧缓冲区。 实现此目的的最简单方法是什么(如果可能,而无需创建另一个帧缓冲区)?
奖励点在于能够使用QOpenGLWidget作为其视口并使用自定义OpenGL绘制的QGraphicsItems捕获屏幕外的QGraphicsView ...
更新2:QOpenGLWidget中相应的错误似乎已在Qt 5.10中修复,因此,我建议再次使用该类。尽管您可能要等待此错误也得到修复...
更新1:添加了3,使用自定义QWindow派生的类的最佳解决方案
1-QOpenGLWidget
如果隐藏的QOpenGLWidget确实分配了帧缓冲区(不确定是否会发生这种情况),则仍然无法手动绑定它,因为您无法获取缓冲区ID。另外,没有必要的函数initializeGL(),resizeGL()和paintGL被调用,并且函数grab(),grabFramebuffer和render()都无法正常工作。这是(imo)一种在屏幕外绘制小部件的解决方法。设置所有必要的东西后,您可以直接调用paintGL:
1 2 3 4 5 6 7 8 | class GLWidget: public QOpenGLWidget { public: GLWidget(QWidget * parent = nullptr); private: bool m_isInitialized = false; QOpenGLFramebufferObject m_fbo = nullptr; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 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
在为render()提供QOpenGLPaintDevice时,它可以按预期工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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,以将帧缓冲区混合到后缓冲区。如果要进行多重采样,可能还需要解析QOpenGLFramebufferObject,以将多重采样的帧缓冲区转换为非多重采样的帧缓冲区。
类接口可以类似于QOpenGLWidget(对于用户来说,是虚拟的initializeGL(),resizeGL(),paintGL())。重新实现ExposureEvent(),resizeEvent()和event()(您可能还需要重新实现metric())。
一个半完整的实现:
标头:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | #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 #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; }; |
资源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 | #include"MyGLWindow.h" #include <QtCore/QCoreApplication> #include <QtGui/QPainter> MyGLWindow::MyGLWindow(QScreen * targetScreen) : QWindow(targetScreen) { //Set Qt::Widget flag to make sure the window resizes properly the first time //when used as a widget via MyGLWindow::createWidget()! setFlags(Qt::Widget); setSurfaceType(QSurface::OpenGLSurface); setFormat(QGLInfo::DefaultSurfaceFormat()); m_initialized = false; m_updatePending = false; create(); initializeInternal(); } MyGLWindow::MyGLWindow(QWindow * parent) : QWindow(parent) { //Set Qt::Widget flag to make sure the window resizes properly the first time //when used as a widget via MyGLWindow::createWidget()! setFlags(Qt::Widget); setSurfaceType(QSurface::OpenGLSurface); setFormat(QGLInfo::DefaultSurfaceFormat()); m_initialized = false; m_updatePending = false; create(); initializeInternal(); } MyGLWindow::~MyGLWindow() { //to delete the FBOs we first need to make the context current m_context->makeCurrent(this); //destroy framebuffer objects if (m_fbo) { m_fbo->release(); delete m_fbo; m_fbo = nullptr; } if (m_resolvedFbo) { m_resolvedFbo->release(); delete m_resolvedFbo; m_resolvedFbo = nullptr; } //destroy shader delete m_blitShader; m_blitShader = nullptr; //free context m_context->doneCurrent(); delete m_context; m_context = nullptr; //free paint device delete m_paintDevice; m_paintDevice = nullptr; m_initialized = false; m_updatePending = false; } QWidget * MyGLWindow::createWidget(QWidget * parent) { QWidget * container = QWidget::createWindowContainer(this, parent); return container; } QOpenGLContext * MyGLWindow::context() const { return m_context; } QOpenGLFunctions * MyGLWindow::functions() const { return m_functions; } GLuint MyGLWindow::framebufferObjectHandle() const { return m_fbo ? m_fbo->handle() : 0; } const QOpenGLFramebufferObject * MyGLWindow::getFramebufferObject() const { return m_fbo; } void MyGLWindow::bindFramebufferObject() { if (m_fbo) { m_fbo->bind(); } else { QOpenGLFramebufferObject::bindDefault(); } } bool MyGLWindow::isValid() const { return (m_initialized && m_context && m_fbo); } void MyGLWindow::makeCurrent() { makeCurrentInternal(); } void MyGLWindow::makeCurrentInternal() { if (isValid()) { m_context->makeCurrent(this); } else { throw("MyGLWindow::makeCurrent() - Window not yet properly initialized!"); } } void MyGLWindow::doneCurrent() { if (m_context) { m_context->doneCurrent(); } } QImage MyGLWindow::grabFramebuffer() { std::lock_guard<std::mutex> locker(m_mutex); makeCurrentInternal(); //blit framebuffer to resolve framebuffer first if needed if (m_fbo->format().samples() > 0) { //check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0 if (m_functions_3_0) { //only blit the color buffer attachment m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle()); m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolvedFbo->handle()); m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, 0); } else { //we must unbind the FBO here, so we can use its texture and bind the default back-buffer m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFbo->handle()); //now use its texture for drawing in the shader --> bind shader and draw textured quad here //bind regular FBO again m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle()); } //check if OpenGL errors happened if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) { qDebug() <<"MyGLWindow::grabFramebuffer() - OpenGL error" << error; } //now grab from resolve FBO return grabFramebufferInternal(m_resolvedFbo); } else { //no multi-sampling. grab directly from FBO return grabFramebufferInternal(m_fbo); } } QImage MyGLWindow::grabFramebufferInternal(QOpenGLFramebufferObject * fbo) { QImage image; //bind framebuffer first m_functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle()); if (m_functions_3_0) { m_functions_3_0->glReadBuffer(GL_COLOR_ATTACHMENT0); } GLenum internalFormat = fbo->format().internalTextureFormat(); bool hasAlpha = internalFormat == GL_RGBA || internalFormat == GL_BGRA || internalFormat == GL_RGBA8; if (internalFormat == GL_BGRA) { image = QImage(fbo->size(), hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32); m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); } else if (internalFormat == GL_RGBA || internalFormat == GL_RGBA8) { image = QImage(fbo->size(), hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888); m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits()); } else { qDebug() <<"MyGLWindow::grabFramebuffer() - Unsupported framebuffer format" << internalFormat <<"!"; } m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle()); return image.mirrored(); } void MyGLWindow::swapBuffers() { swapBuffersInternal(); emit frameSwapped(); } void MyGLWindow::swapBuffersInternal() { if (isExposed() && isVisible()) { //blit framebuffer to back buffer m_context->makeCurrent(this); //make sure all paint operation have been processed m_functions->glFlush(); //check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0 if (m_functions_3_0) { //if our framebuffer has multi-sampling, the resolve should be done automagically m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle()); m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); //blit all buffers including depth buffer for further rendering m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle()); } else { //we must unbind the FBO here, so we can use its texture and bind the default back-buffer m_functions->glBindFramebuffer(GL_FRAMEBUFFER, 0); //now use its texture for drawing in the shader --> bind shader and draw textured quad here //bind regular FBO again m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle()); } //check if OpenGL errors happened if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) { qDebug() <<"MyGLWindow::swapBuffersInternal() - OpenGL error" << error; } //now swap back buffer to front buffer m_context->swapBuffers(this); } else { //not visible. only flush the pipeline so we can possibly grab the FBO later m_context->makeCurrent(this); m_functions->glFlush(); } } void MyGLWindow::recreateFBOAndPaintDevice() { const QSize deviceSize = size() * devicePixelRatio(); if (m_context && (m_fbo == nullptr || m_fbo->size() != deviceSize)) { m_context->makeCurrent(this); //free old FBOs if (m_fbo) { m_fbo->release(); delete m_fbo; m_fbo = nullptr; } if (m_resolvedFbo) { m_resolvedFbo->release(); delete m_resolvedFbo; m_resolvedFbo = nullptr; } //create new frame buffer QOpenGLFramebufferObjectFormat format = QGLInfo::DefaultFramebufferFormat(); format.setSamples(QGLInfo::HasMultisamplingSupport(m_context) ? 4 : 0); m_fbo = new QOpenGLFramebufferObject(deviceSize, format); if (!m_fbo->isValid()) { throw("MyGLWindow::recreateFbo() - Failed to create background FBO!"); } //clear framebuffer m_fbo->bind(); m_functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); m_fbo->release(); //if multi sampling is requested and supported we need a resolve FBO if (format.samples() > 0) { //create resolve framebuffer with only a color attachment format.setAttachment(QOpenGLFramebufferObject::NoAttachment); format.setSamples(0); m_resolvedFbo = new QOpenGLFramebufferObject(deviceSize, format); if (!m_resolvedFbo->isValid()) { throw("MyGLWindow::recreateFbo() - Failed to create resolve FBO!"); } //clear resolve framebuffer m_resolvedFbo->bind(); m_functions->glClear(GL_COLOR_BUFFER_BIT); m_resolvedFbo->release(); } } //create paint device for painting with QPainter if needed if (!m_paintDevice) { m_paintDevice = new QOpenGLPaintDevice; } //update paint device size if needed if (m_paintDevice->size() != deviceSize) { m_paintDevice->setDevicePixelRatio(devicePixelRatio()); m_paintDevice->setSize(deviceSize); } } void MyGLWindow::initializeInternal() { if (!m_initialized.exchange(true)) { //create OpenGL context. we set the format requested by the user (default: QWindow::requestedFormat()) m_context = new QOpenGLContext(this); m_context->setFormat(format()); if (m_context->create()) { m_context->makeCurrent(this); //initialize the OpenGL 2.1 / ES 2.0 functions for this object m_functions = m_context->functions(); m_functions->initializeOpenGLFunctions(); //try initializing the OpenGL 3.0 functions for this object m_functions_3_0 = m_context->versionFunctions<QOpenGLFunctions_3_0>(); if (m_functions_3_0) { m_functions_3_0->initializeOpenGLFunctions(); } else { //if we do not have OpenGL 3.0 functions, glBlitFrameBuffer is not available, so we must do the blit //using a shader and the framebuffer texture, so we need to create the shader here... --> allocate m_blitShader, a simple shader for drawing a textured quad --> build quad geometry, VBO, whatever } //now we have a context, create the FBO recreateFBOAndPaintDevice(); } else { m_initialized = false; delete m_context; m_context = nullptr; throw("Failed to create OpenGL context!"); } } } void MyGLWindow::update() { //only queue an update if there's not already an update pending if (!m_updatePending.exchange(true)) { QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); } } void MyGLWindow::render() { std::lock_guard<std::mutex> locker(m_mutex); //check if we need to initialize stuff initializeInternal(); //check if we need to call the user initialization if (!m_initializedGL) { m_initializedGL = true; initializeGL(); } //make context current and bind framebuffer makeCurrent(); bindFramebufferObject(); //call user paint function paintGL(); doneCurrent(); //mark that we're done with updating m_updatePending = false; } void MyGLWindow::exposeEvent(QExposeEvent * e) { //call base implementation QWindow::exposeEvent(e); //render window content if window is exposed if (isExposed()/* && isVisible()*/) { render(); } } void MyGLWindow::resizeEvent(QResizeEvent *e) { //call base implementation QWindow::resizeEvent(e); m_mutex.lock(); //make context current first makeCurrent(); //update FBO and paint device recreateFBOAndPaintDevice(); m_mutex.unlock(); //call user-defined resize method resizeGL(e->size().width(), e->size().height()); emit resized(); } bool MyGLWindow::event(QEvent *event) { switch (event->type()) { case QEvent::UpdateLater: update(); return true; case QEvent::UpdateRequest: render(); return true; default: return QWindow::event(event); } } |
对于屏幕外渲染,我们还可以使用QOffscreenSurface。
这是基于Bim的MyGLWindow的工作类(并非全部经过测试):
标头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | #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 #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 |
来源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 | #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); } |
使用
标头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #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 |
来源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #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(); } |
来源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #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的部分示例来自此处:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #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; } |
这是一个疑问:
For offscreen rendering, try rendering into a
QOpenGLFramebufferObject, which can be converted into a
QImage , which in turn can easily be saved to disk.For that however, you still need a surface to render onto (as required
byQOpenGLContext::makeCurrent() ), so your only choice is
indeed using aQWindow or aQGLWidget to get such a surface.In Qt 5.1, there will be a new class called
QOffscreenSurface,
which will probably be most suitable for your usecase. You would use
QOffscreenSurface to get a surface for your OpenGL
context, and then render into a FBO using
QOpenGLFramebufferObject , and then call
QOpenGLFramebufferObject::toImage() to get access to the
pixels.