使用 QOpenGLWidget 进行离屏渲染的最简单方法
Easiest way for offscreen rendering with QOpenGLWidget
我有一个隐藏的 QOpenGLWidget(Qt 5.4.2,不是 QGLWidget),我想基本上不断地执行 grab() 或 grabFramebuffer() 来获取它的内容(并将其写入磁盘)。小部件在可见时呈现良好,但在隐藏时则不会。如果我执行一个 show() 后跟一个 hide() 调用,它就可以工作。这看起来很奇怪,因为根据文档,QOpenGLWidget 在内部已经渲染到帧缓冲区。实现此目的的最简单方法是什么(如果可能,无需创建另一个帧缓冲区)?
能够使用 QOpenGLWidget 作为其视口并在其中使用自定义 OpenGL 绘制的 QGraphicsItems 捕获屏幕外 QGraphicsView 的奖励积分...
更新 2:corresponding bug in QOpenGLWidget seems to be fixed in Qt 5.10, so I suggest to simply use the class again. Although you might want to wait for this bug 也得到修复...
更新 1:添加了 3,使用自定义的最佳解决方案 QWindow-derived class
1 - QOpenGLWidget
如果隐藏的 QOpenGLWidget 确实分配了一个帧缓冲区(不确定是否会发生这种情况),仍然无法手动绑定它,因为您无法获取缓冲区 ID。此外 none 的必要函数 initializeGL()、resizeGL() 和 paintGL 被调用,并且 none 的函数 grab()、grabFramebuffer 和 render() 正常工作。这是(imo)将小部件绘制到屏幕外的解决方法。设置完所有必要的东西后直接调用 paintGL:
class GLWidget: public QOpenGLWidget
{
public:
GLWidget(QWidget * parent = nullptr);
private:
bool m_isInitialized = false;
QOpenGLFramebufferObject m_fbo = nullptr;
};
void GLWidget::drawOffscreen()
{
//the context should be valid. make sure it is current for painting
makeCurrent();
if (!m_isInitialized)
{
initializeGL();
resizeGL(width(), height());
}
if (!m_fbo || m_fbo->width() != width() || m_fbo->height() != height())
{
//allocate additional? FBO for rendering or resize it if widget size changed
delete m_fbo;
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
m_fbo = new QOpenGLFramebufferObject(width(), height(), format);
resizeGL(width(), height());
}
//#1 DOES NOT WORK: bind FBO and render() widget
m_fbo->bind();
QOpenGLPaintDevice fboPaintDev(width(), height());
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
render(&painter);
painter.end();
//You could now grab the content of the framebuffer we've rendered to
QImage image1 = m_fbo->toImage();
image1.save(QString("fb1.png"));
m_fbo->release();
//#1 --------------------------------------------------------------
//#2 WORKS: bind FBO and render stuff with paintGL() call
m_fbo->bind();
paintGL();
//You could now grab the content of the framebuffer we've rendered to
QImage image2 = m_fbo->toImage();
image2.save(QString("fb2.png"));
m_fbo->release();
//#2 --------------------------------------------------------------
//bind default framebuffer again. not sure if this necessary
//and isn't supposed to use defaultFramebuffer()...
m_fbo->bindDefault();
doneCurrent();
}
void GLWidget::paintGL()
{
//When doing mixed QPainter/OpenGL rendering make sure to use a QOpenGLPaintDevice, otherwise only OpenGL content is visible!
//I'm not sure why, because according to the docs (http://doc.qt.io/qt-5/topics-graphics.html) this is supposed to be the same...
QOpenGLPaintDevice fboPaintDev(width(), height());
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
//This is what you'd use (and what would work) if the widget was visible
//QPainter painter;
//painter.begin(this);
//now start OpenGL painting
painter.beginNativePainting();
glClearColor(0.5f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
...
painter.endNativePainting();
//draw non-OpenGL stuff with QPainter
painter.drawText(20, 40, "Foo");
...
painter.end();
}
2 - 带有 QOpenGLWidget 视口的 QGraphicsView
当您为它提供 QOpenGLPaintDevice:
时,这里的 render() 会按预期工作
MainWindow::MainWindow()
{
scene = new QGraphicsScene;
hiddenView = new QGraphicsView(scene);
hiddenGLWidget = new QOpenGLWidget;
hiddenView->setViewport(hiddenGLWidget);
//hiddenView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
//hiddenView->show();
}
void MainWindow::screenshot()
{
//try regular grab functions
QPixmap pixmap1 = hiddenView->grab(); //image with scrollbars, no OpenGL content
pixmap1.save("bla1.png");
QPixmap pixmap2 = hiddenGLWidget->grab(); //produces an empty image
pixmap2.save("bla2.png");
//try grabbing only the QOpenGLWidget framebuffer
QImage image1 = hiddenGLWidget->grabFramebuffer(); //null image
image1.save("bla3.png");
//WORKS: render via FBO
hiddenGLWidget->makeCurrent();
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObject * fbo = new QOpenGLFramebufferObject(hiddenView->width(), hiddenView->height(), format);
fbo->bind();
QOpenGLPaintDevice fboPaintDev(hiddenView->width(), hiddenView->height());
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
hiddenView->render(&painter); //WORKS and captures mixed OpenGL and non-OpenGL QGraphicsitems
//hiddenView->repaint(); //does not work
//hiddenView->scene()->render(&painter); //does not work
//hiddenGLWidget->paintGL(); //might work. can not call, protected
//hiddenGLWidget->render(&painter); //does not work
//hiddenGLWidget->repaint(); //does not work
painter.end();
QImage image2 = fbo->toImage();
image2.save("bla4.png");
fbo->release();
delete fbo;
}
3 - 如何从隐藏的 QOpenGLWidget 渲染和抓取图像
更好的整体解决方案是使用具有 QSurface::OpenGLSurface 类型的自定义 QWindow。创建一个额外的 QOpenGLContext,一个您将绘制到的额外背景 QOpenGLFramebufferObject,以及一个 QOpenGLShaderProgram 以将帧缓冲区 blit 到后台缓冲区。如果你想要多重采样,你可能还需要一个解析 QOpenGLFramebufferObject,将多重采样帧缓冲区转换为 non-multisampled 帧缓冲区。
class接口可以类似于QOpenGLWidget(为用户虚拟initializeGL()、resizeGL()、paintGL())。重新实现 exposeEvent()、resizeEvent() 和 event()(您可能也需要重新实现 metric())。
semi-complete 实现:
Header:
#pragma once
#include <QtCore/QObject>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtGui/QPaintEvent>
#include <QtGui/QResizeEvent>
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLFunctions_3_0>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QSurfaceFormat>
#include <QtWidgets/QWidget>
#include <atomic>
#include <mutex>
class MyGLWindow : public QWindow
{
Q_OBJECT
public:
/// @brief Constructor. Creates a render window.
/// @param targetScreen Target screen.
/// this is because before the FBO and off-screen surface haven't been created.
/// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen surface.
explicit MyGLWindow(QScreen * targetScreen = nullptr);
/// @brief Constructor. Creates a render window.
/// @param parent Parent window.
/// this is because before the FBO and off-screen surface haven't been created.
/// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen surface.
explicit MyGLWindow(QWindow * parent);
/// @brief Destructor.
virtual ~MyGLWindow();
/// @brief Create a container widget for this window.
/// @param parent Parent widget.
/// @return Returns a container widget for the window.
QWidget * createWidget(QWidget * parent = nullptr);
/// @brief Check if the window is initialized and can be used for rendering.
/// @return Returns true if context, surface and FBO have been set up to start rendering.
bool isValid() const;
/// @brief Return the context used in this window.
/// @return The context used in this window or nullptr if it hasn't been created yet.
QOpenGLContext * context() const;
/// @brief Return the OpenGL function object that can be used the issue OpenGL commands.
/// @return The functions for the context or nullptr if it the context hasn't been created yet.
QOpenGLFunctions * functions() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created yet.
/// @note This changes on every resize!
GLuint framebufferObjectHandle() const;
/// @brief Return the OpenGL off-screen frame buffer object.
/// @return The OpenGL off-screen frame buffer object or nullptr if no FBO has been created yet.
/// @note This changes on every resize!
const QOpenGLFramebufferObject * getFramebufferObject() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created yet.
void bindFramebufferObject();
/// @brief Return the current contents of the FBO.
/// @return FBO content as 32bit QImage. You might need to swap RGBA to BGRA or vice-versa.
QImage grabFramebuffer();
/// @brief Makes the OpenGL context current for rendering.
/// @note Make sure to bindFramebufferObject() if you want to render to this widgets FBO.
void makeCurrent();
/// @brief Release the OpenGL context.
void doneCurrent();
/// @brief Copy content of framebuffer to back buffer and swap buffers if the surface is double-buffered.
/// If the surface is not double-buffered, the frame buffer content is blitted to the front buffer.
/// If the window is not exposed, only the OpenGL pipeline is glFlush()ed so the framebuffer can be read back.
void swapBuffers();
public slots:
/// @brief Lazy update routine like QWidget::update().
void update();
/// @brief Immediately render the widget contents to framebuffer.
void render();
signals:
/// @brief Emitted when swapBuffers() was called and bufferswapping is done.
void frameSwapped();
/// @brief Emitted after a resizeEvent().
void resized();
protected:
virtual void exposeEvent(QExposeEvent *e) override;
virtual void resizeEvent(QResizeEvent *e) override;
virtual bool event(QEvent *e) override;
// virtual int metric(QPaintDevice::PaintDeviceMetric metric) const override;
/// @brief Called exactly once when the window is first exposed OR render() is called when the widget is invisible.
/// @note After this the off-screen surface and FBO are available.
virtual void initializeGL() = 0;
/// @brief Called whenever the window size changes.
/// @param width New window width.
/// @param height New window height.
virtual void resizeGL(int width, int height) = 0;
/// @brief Called whenever the window needs to repaint itself. Override to draw OpenGL content.
/// When this function is called, the context is already current and the correct framebuffer is bound.
virtual void paintGL() = 0;
// /// @brief Called whenever the window needs to repaint itself. Override to draw QPainter content.
// /// @brief This is called AFTER paintGL()! Only needed when painting using a QPainter.
// virtual void paintEvent(QPainter & painter) = 0;
private:
Q_DISABLE_COPY(QGLWindow)
/// @brief Initialize the window.
void initializeInternal();
/// @brief Internal method that does the actual swap work, NOT using a mutex.
void swapBuffersInternal();
/// @brief Internal method that checks state and makes the context current, NOT using a mutex.
void makeCurrentInternal();
/// @brief Internal method to grab content of a specific framebuffer.
QImage grabFramebufferInternal(QOpenGLFramebufferObject * fbo);
/// @brief (Re-)allocate FBO and paint device if needed due to size changes etc.
void recreateFBOAndPaintDevice();
/// @brief False before the window was first exposed OR render() was called.
std::atomic_bool m_initialized;
/// @brief False before the overridden initializeGL() was first called.
bool m_initializedGL = false;
/// @brief True when currently a window update is pending.
std::atomic_bool m_updatePending;
/// @brief Mutex making sure not grabbing while drawing etc.
std::mutex m_mutex;
/// @brief OpenGL render context.
QOpenGLContext * m_context = nullptr;
/// @brief The OpenGL 2.1 / ES 2.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions * m_functions = nullptr;
/// @brief The OpenGL 3.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions_3_0 * m_functions_3_0 = nullptr;
/// @brief OpenGL paint device for painting with a QPainter.
QOpenGLPaintDevice * m_paintDevice = nullptr;
/// @brief Background FBO for off-screen rendering when the window is not exposed.
QOpenGLFramebufferObject * m_fbo = nullptr;
/// @brief Background FBO resolving a multi sampling frame buffer in m_fbo to a frame buffer
/// that can be grabbed to a QImage.
QOpenGLFramebufferObject * m_resolvedFbo = nullptr;
/// @brief Shader used for blitting m_fbo to screen if glBlitFrameBuffer is not available.
QOpenGLShaderProgram * m_blitShader;
};
来源:
#include "MyGLWindow.h"
#include <QtCore/QCoreApplication>
#include <QtGui/QPainter>
MyGLWindow::MyGLWindow(QScreen * targetScreen)
: QWindow(targetScreen)
{
//Set Qt::Widget flag to make sure the window resizes properly the first time
//when used as a widget via MyGLWindow::createWidget()!
setFlags(Qt::Widget);
setSurfaceType(QSurface::OpenGLSurface);
setFormat(QGLInfo::DefaultSurfaceFormat());
m_initialized = false;
m_updatePending = false;
create();
initializeInternal();
}
MyGLWindow::MyGLWindow(QWindow * parent)
: QWindow(parent)
{
//Set Qt::Widget flag to make sure the window resizes properly the first time
//when used as a widget via MyGLWindow::createWidget()!
setFlags(Qt::Widget);
setSurfaceType(QSurface::OpenGLSurface);
setFormat(QGLInfo::DefaultSurfaceFormat());
m_initialized = false;
m_updatePending = false;
create();
initializeInternal();
}
MyGLWindow::~MyGLWindow()
{
//to delete the FBOs we first need to make the context current
m_context->makeCurrent(this);
//destroy framebuffer objects
if (m_fbo)
{
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo)
{
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
//destroy shader
delete m_blitShader;
m_blitShader = nullptr;
//free context
m_context->doneCurrent();
delete m_context;
m_context = nullptr;
//free paint device
delete m_paintDevice;
m_paintDevice = nullptr;
m_initialized = false;
m_updatePending = false;
}
QWidget * MyGLWindow::createWidget(QWidget * parent)
{
QWidget * container = QWidget::createWindowContainer(this, parent);
return container;
}
QOpenGLContext * MyGLWindow::context() const
{
return m_context;
}
QOpenGLFunctions * MyGLWindow::functions() const
{
return m_functions;
}
GLuint MyGLWindow::framebufferObjectHandle() const
{
return m_fbo ? m_fbo->handle() : 0;
}
const QOpenGLFramebufferObject * MyGLWindow::getFramebufferObject() const
{
return m_fbo;
}
void MyGLWindow::bindFramebufferObject()
{
if (m_fbo)
{
m_fbo->bind();
}
else
{
QOpenGLFramebufferObject::bindDefault();
}
}
bool MyGLWindow::isValid() const
{
return (m_initialized && m_context && m_fbo);
}
void MyGLWindow::makeCurrent()
{
makeCurrentInternal();
}
void MyGLWindow::makeCurrentInternal()
{
if (isValid())
{
m_context->makeCurrent(this);
}
else
{
throw("MyGLWindow::makeCurrent() - Window not yet properly initialized!");
}
}
void MyGLWindow::doneCurrent()
{
if (m_context)
{
m_context->doneCurrent();
}
}
QImage MyGLWindow::grabFramebuffer()
{
std::lock_guard<std::mutex> locker(m_mutex);
makeCurrentInternal();
//blit framebuffer to resolve framebuffer first if needed
if (m_fbo->format().samples() > 0)
{
//check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0
if (m_functions_3_0)
{
//only blit the color buffer attachment
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolvedFbo->handle());
m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(),
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
else
{
//we must unbind the FBO here, so we can use its texture and bind the default back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFbo->handle());
//now use its texture for drawing in the shader
--> bind shader and draw textured quad here
//bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
//check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR)
{
qDebug() << "MyGLWindow::grabFramebuffer() - OpenGL error" << error;
}
//now grab from resolve FBO
return grabFramebufferInternal(m_resolvedFbo);
}
else
{
//no multi-sampling. grab directly from FBO
return grabFramebufferInternal(m_fbo);
}
}
QImage MyGLWindow::grabFramebufferInternal(QOpenGLFramebufferObject * fbo)
{
QImage image;
//bind framebuffer first
m_functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
if (m_functions_3_0)
{
m_functions_3_0->glReadBuffer(GL_COLOR_ATTACHMENT0);
}
GLenum internalFormat = fbo->format().internalTextureFormat();
bool hasAlpha = internalFormat == GL_RGBA || internalFormat == GL_BGRA || internalFormat == GL_RGBA8;
if (internalFormat == GL_BGRA)
{
image = QImage(fbo->size(), hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
}
else if (internalFormat == GL_RGBA || internalFormat == GL_RGBA8)
{
image = QImage(fbo->size(), hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888);
m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
}
else
{
qDebug() << "MyGLWindow::grabFramebuffer() - Unsupported framebuffer format" << internalFormat << "!";
}
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
return image.mirrored();
}
void MyGLWindow::swapBuffers()
{
swapBuffersInternal();
emit frameSwapped();
}
void MyGLWindow::swapBuffersInternal()
{
if (isExposed() && isVisible())
{
//blit framebuffer to back buffer
m_context->makeCurrent(this);
//make sure all paint operation have been processed
m_functions->glFlush();
//check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0
if (m_functions_3_0)
{
//if our framebuffer has multi-sampling, the resolve should be done automagically
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
//blit all buffers including depth buffer for further rendering
m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(),
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
else
{
//we must unbind the FBO here, so we can use its texture and bind the default back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, 0);
//now use its texture for drawing in the shader
--> bind shader and draw textured quad here
//bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
//check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR)
{
qDebug() << "MyGLWindow::swapBuffersInternal() - OpenGL error" << error;
}
//now swap back buffer to front buffer
m_context->swapBuffers(this);
}
else
{
//not visible. only flush the pipeline so we can possibly grab the FBO later
m_context->makeCurrent(this);
m_functions->glFlush();
}
}
void MyGLWindow::recreateFBOAndPaintDevice()
{
const QSize deviceSize = size() * devicePixelRatio();
if (m_context && (m_fbo == nullptr || m_fbo->size() != deviceSize))
{
m_context->makeCurrent(this);
//free old FBOs
if (m_fbo)
{
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo)
{
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
//create new frame buffer
QOpenGLFramebufferObjectFormat format = QGLInfo::DefaultFramebufferFormat();
format.setSamples(QGLInfo::HasMultisamplingSupport(m_context) ? 4 : 0);
m_fbo = new QOpenGLFramebufferObject(deviceSize, format);
if (!m_fbo->isValid())
{
throw("MyGLWindow::recreateFbo() - Failed to create background FBO!");
}
//clear framebuffer
m_fbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
m_fbo->release();
//if multi sampling is requested and supported we need a resolve FBO
if (format.samples() > 0)
{
//create resolve framebuffer with only a color attachment
format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
format.setSamples(0);
m_resolvedFbo = new QOpenGLFramebufferObject(deviceSize, format);
if (!m_resolvedFbo->isValid())
{
throw("MyGLWindow::recreateFbo() - Failed to create resolve FBO!");
}
//clear resolve framebuffer
m_resolvedFbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT);
m_resolvedFbo->release();
}
}
//create paint device for painting with QPainter if needed
if (!m_paintDevice)
{
m_paintDevice = new QOpenGLPaintDevice;
}
//update paint device size if needed
if (m_paintDevice->size() != deviceSize)
{
m_paintDevice->setDevicePixelRatio(devicePixelRatio());
m_paintDevice->setSize(deviceSize);
}
}
void MyGLWindow::initializeInternal()
{
if (!m_initialized.exchange(true))
{
//create OpenGL context. we set the format requested by the user (default: QWindow::requestedFormat())
m_context = new QOpenGLContext(this);
m_context->setFormat(format());
if (m_context->create())
{
m_context->makeCurrent(this);
//initialize the OpenGL 2.1 / ES 2.0 functions for this object
m_functions = m_context->functions();
m_functions->initializeOpenGLFunctions();
//try initializing the OpenGL 3.0 functions for this object
m_functions_3_0 = m_context->versionFunctions<QOpenGLFunctions_3_0>();
if (m_functions_3_0)
{
m_functions_3_0->initializeOpenGLFunctions();
}
else
{
//if we do not have OpenGL 3.0 functions, glBlitFrameBuffer is not available, so we must do the blit
//using a shader and the framebuffer texture, so we need to create the shader here...
--> allocate m_blitShader, a simple shader for drawing a textured quad
--> build quad geometry, VBO, whatever
}
//now we have a context, create the FBO
recreateFBOAndPaintDevice();
}
else
{
m_initialized = false;
delete m_context;
m_context = nullptr;
throw("Failed to create OpenGL context!");
}
}
}
void MyGLWindow::update()
{
//only queue an update if there's not already an update pending
if (!m_updatePending.exchange(true))
{
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
}
}
void MyGLWindow::render()
{
std::lock_guard<std::mutex> locker(m_mutex);
//check if we need to initialize stuff
initializeInternal();
//check if we need to call the user initialization
if (!m_initializedGL)
{
m_initializedGL = true;
initializeGL();
}
//make context current and bind framebuffer
makeCurrent();
bindFramebufferObject();
//call user paint function
paintGL();
doneCurrent();
//mark that we're done with updating
m_updatePending = false;
}
void MyGLWindow::exposeEvent(QExposeEvent * e)
{
//call base implementation
QWindow::exposeEvent(e);
//render window content if window is exposed
if (isExposed()/* && isVisible()*/)
{
render();
}
}
void MyGLWindow::resizeEvent(QResizeEvent *e)
{
//call base implementation
QWindow::resizeEvent(e);
m_mutex.lock();
//make context current first
makeCurrent();
//update FBO and paint device
recreateFBOAndPaintDevice();
m_mutex.unlock();
//call user-defined resize method
resizeGL(e->size().width(), e->size().height());
emit resized();
}
bool MyGLWindow::event(QEvent *event)
{
switch (event->type())
{
case QEvent::UpdateLater:
update();
return true;
case QEvent::UpdateRequest:
render();
return true;
default:
return QWindow::event(event);
}
}
对于离屏渲染我们也可以使用QOffscreenSurface。
这里是基于 Bim 的 MyGLWindow class 工作的(并非所有都经过测试):
Header OpenGlOffscreenSurface.h
:
#ifndef OPENGLOFFSCREENSURFACE_H
#define OPENGLOFFSCREENSURFACE_H
#pragma once
#include <QtCore/QObject>
#include <QtGui/QScreen>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QPaintEvent>
#include <QtGui/QResizeEvent>
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLFunctions_3_0>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QSurfaceFormat>
#include <QtWidgets/QWidget>
#include <QOpenGLShaderProgram>
#include <atomic>
#include <mutex>
class OpenGlOffscreenSurface
: public QOffscreenSurface
{
Q_OBJECT
public:
/// @brief Constructor. Creates a render window.
/// @param targetScreen Target screen.
/// @param size Initial size of a surface buffer.
/// this is because before the FBO and off-screen surface haven't been created.
/// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen
/// surface.
explicit OpenGlOffscreenSurface(
QScreen* targetScreen = nullptr,
const QSize& size = QSize (1, 1));
/// @brief Destructor.
virtual ~OpenGlOffscreenSurface();
/// @brief Check if the window is initialized and can be used for rendering.
/// @return Returns true if context, surface and FBO have been set up to start rendering.
bool isValid() const;
/// @brief Return the context used in this window.
/// @return The context used in this window or nullptr if it hasn't been created yet.
QOpenGLContext* context() const;
/// @brief Return the OpenGL function object that can be used the issue OpenGL commands.
/// @return The functions for the context or nullptr if it the context hasn't been created yet.
QOpenGLFunctions* functions() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created
/// yet.
/// @note This changes on every resize!
GLuint framebufferObjectHandle() const;
/// @brief Return the OpenGL off-screen frame buffer object.
/// @return The OpenGL off-screen frame buffer object or nullptr if no FBO has been created yet.
/// @note This changes on every resize!
const QOpenGLFramebufferObject* getFramebufferObject() const;
/// @brief Return the QPaintDevice for paint into it.
QPaintDevice* getPaintDevice() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created
/// yet.
void bindFramebufferObject();
/// @brief Return the current contents of the FBO.
/// @return FBO content as 32bit QImage. You might need to swap RGBA to BGRA or vice-versa.
QImage grabFramebuffer();
/// @brief Makes the OpenGL context current for rendering.
/// @note Make sure to bindFramebufferObject() if you want to render to this widgets FBO.
void makeCurrent();
/// @brief Release the OpenGL context.
void doneCurrent();
/// @brief Copy content of framebuffer to back buffer and swap buffers if the surface is
/// double-buffered.
/// If the surface is not double-buffered, the frame buffer content is blitted to the front
/// buffer.
/// If the window is not exposed, only the OpenGL pipeline is glFlush()ed so the framebuffer can
/// be read back.
void swapBuffers();
/// @brief Use bufferSize() instead size() for get a size of a surface buffer. We can't override size() as it is not virtual.
QSize bufferSize() const;
/// @brief Resize surface buffer to newSize.
void resize(const QSize& newSize);
/// @brief Resize surface buffer to size with width w and height h.
/// @param w Width.
/// @param h Height.
void resize(int w, int h);
public slots:
/// @brief Lazy update routine like QWidget::update().
void update();
/// @brief Immediately render the widget contents to framebuffer.
void render();
signals:
/// @brief Emitted when swapBuffers() was called and bufferswapping is done.
void frameSwapped();
/// @brief Emitted after a resizeEvent().
void resized();
protected:
virtual void exposeEvent(QExposeEvent* e);
virtual void resizeEvent(QResizeEvent* e);
virtual bool event(QEvent* e) override;
// virtual int metric(QPaintDevice::PaintDeviceMetric metric) const override;
/// @brief Called exactly once when the window is first exposed OR render() is called when the
/// widget is invisible.
/// @note After this the off-screen surface and FBO are available.
virtual void initializeGL() = 0;
/// @brief Called whenever the window size changes.
/// @param width New window width.
/// @param height New window height.
virtual void resizeGL(
int width,
int height) = 0;
/// @brief Called whenever the window needs to repaint itself. Override to draw OpenGL content.
/// When this function is called, the context is already current and the correct framebuffer is
/// bound.
virtual void paintGL() = 0;
// /// @brief Called whenever the window needs to repaint itself. Override to draw QPainter
// content.
// /// @brief This is called AFTER paintGL()! Only needed when painting using a QPainter.
// virtual void paintEvent(QPainter & painter) = 0;
private:
Q_DISABLE_COPY(OpenGlOffscreenSurface)
/// @brief Initialize the window.
void initializeInternal();
/// @brief Internal method that does the actual swap work, NOT using a mutex.
void swapBuffersInternal();
/// @brief Internal method that checks state and makes the context current, NOT using a mutex.
void makeCurrentInternal();
/// @brief Internal method to grab content of a specific framebuffer.
QImage grabFramebufferInternal(QOpenGLFramebufferObject* fbo);
/// @brief (Re-)allocate FBO and paint device if needed due to size changes etc.
void recreateFBOAndPaintDevice();
/// @brief False before the window was first exposed OR render() was called.
std::atomic_bool m_initialized;
/// @brief False before the overridden initializeGL() was first called.
bool m_initializedGL = false;
/// @brief True when currently a window update is pending.
std::atomic_bool m_updatePending;
/// @brief Mutex making sure not grabbing while drawing etc.
std::mutex m_mutex;
/// @brief OpenGL render context.
QOpenGLContext* m_context = nullptr;
/// @brief The OpenGL 2.1 / ES 2.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions* m_functions = nullptr;
/// @brief The OpenGL 3.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions_3_0* m_functions_3_0 = nullptr;
/// @brief OpenGL paint device for painting with a QPainter.
QOpenGLPaintDevice* m_paintDevice = nullptr;
/// @brief Background FBO for off-screen rendering when the window is not exposed.
QOpenGLFramebufferObject* m_fbo = nullptr;
/// @brief Background FBO resolving a multi sampling frame buffer in m_fbo to a frame buffer
/// that can be grabbed to a QImage.
QOpenGLFramebufferObject* m_resolvedFbo = nullptr;
/// @brief Shader used for blitting m_fbo to screen if glBlitFrameBuffer is not available.
QOpenGLShaderProgram* m_blitShader;
QSize m_size;
};
#endif // OPENGLOFFSCREENSURFACE_H
来源OpenGlOffscreenSurface.cpp
:
#include "OpenGlOffscreenSurface.h"
#include <QtCore/QCoreApplication>
#include <QtGui/QPainter>
OpenGlOffscreenSurface::OpenGlOffscreenSurface(
QScreen* targetScreen,
const QSize& size)
: QOffscreenSurface(targetScreen)
, m_size(size)
{
setFormat(QSurfaceFormat::defaultFormat());
m_initialized = false;
m_updatePending = false;
create(); // Some platforms require this function to be called on the main (GUI) thread
initializeInternal();
}
OpenGlOffscreenSurface::~OpenGlOffscreenSurface()
{
// to delete the FBOs we first need to make the context current
m_context->makeCurrent(this);
// destroy framebuffer objects
if (m_fbo) {
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo) {
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
// destroy shader
delete m_blitShader;
m_blitShader = nullptr;
// free context
m_context->doneCurrent();
delete m_context;
m_context = nullptr;
// free paint device
delete m_paintDevice;
m_paintDevice = nullptr;
m_initialized = false;
m_updatePending = false;
destroy();
}
QOpenGLContext* OpenGlOffscreenSurface::context() const
{
return (m_context);
}
QOpenGLFunctions* OpenGlOffscreenSurface::functions() const
{
return (m_functions);
}
GLuint OpenGlOffscreenSurface::framebufferObjectHandle() const
{
return (m_fbo ? m_fbo->handle() : 0);
}
const QOpenGLFramebufferObject* OpenGlOffscreenSurface::getFramebufferObject() const
{
return (m_fbo);
}
QPaintDevice* OpenGlOffscreenSurface::getPaintDevice() const
{
return (m_paintDevice);
}
void OpenGlOffscreenSurface::bindFramebufferObject()
{
if (m_fbo) {
m_fbo->bind();
} else {
QOpenGLFramebufferObject::bindDefault();
}
}
bool OpenGlOffscreenSurface::isValid() const
{
return (m_initialized && m_context && m_fbo);
}
void OpenGlOffscreenSurface::makeCurrent()
{
makeCurrentInternal();
}
void OpenGlOffscreenSurface::makeCurrentInternal()
{
if (isValid()) {
m_context->makeCurrent(this);
} else {
throw ("OpenGlOffscreenSurface::makeCurrent() - Window not yet properly initialized!");
}
}
void OpenGlOffscreenSurface::doneCurrent()
{
if (m_context) {
m_context->doneCurrent();
}
}
QImage OpenGlOffscreenSurface::grabFramebuffer()
{
std::lock_guard <std::mutex> locker(m_mutex);
makeCurrentInternal();
// blit framebuffer to resolve framebuffer first if needed
if (m_fbo->format().samples() > 0) {
// check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not
// OpenGL ES 2.0
if (m_functions_3_0) {
// only blit the color buffer attachment
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolvedFbo->handle());
m_functions_3_0->glBlitFramebuffer(0, 0, bufferSize().width(),
bufferSize().height(), 0, 0, bufferSize().width(),
bufferSize().height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, 0);
} else {
// we must unbind the FBO here, so we can use its texture and bind the default
// back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFbo->handle());
// now use its texture for drawing in the shader
// --> bind shader and draw textured quad here
// bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
// check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) {
qDebug() << "OpenGlOffscreenSurface::grabFramebuffer() - OpenGL error" << error;
}
// now grab from resolve FBO
return (grabFramebufferInternal(m_resolvedFbo));
} else {
// no multi-sampling. grab directly from FBO
return (grabFramebufferInternal(m_fbo));
}
} // OpenGlOffscreenSurface::grabFramebuffer
QImage OpenGlOffscreenSurface::grabFramebufferInternal(QOpenGLFramebufferObject* fbo)
{
QImage image;
// bind framebuffer first
m_functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
if (m_functions_3_0) {
m_functions_3_0->glReadBuffer(GL_COLOR_ATTACHMENT0);
}
GLenum internalFormat = fbo->format().internalTextureFormat();
bool hasAlpha = internalFormat == GL_RGBA || internalFormat == GL_BGRA
|| internalFormat == GL_RGBA8;
if (internalFormat == GL_BGRA) {
image = QImage(fbo->size(), hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
m_functions->glReadPixels(0, 0, fbo->size().width(),
fbo->size().height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
} else if ((internalFormat == GL_RGBA) || (internalFormat == GL_RGBA8)) {
image = QImage(fbo->size(), hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888);
m_functions->glReadPixels(0, 0, fbo->size().width(),
fbo->size().height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
} else {
qDebug() << "OpenGlOffscreenSurface::grabFramebuffer() - Unsupported framebuffer format"
<< internalFormat << "!";
}
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
return (image.mirrored());
} // OpenGlOffscreenSurface::grabFramebufferInternal
void OpenGlOffscreenSurface::swapBuffers()
{
swapBuffersInternal();
emit frameSwapped();
}
void OpenGlOffscreenSurface::swapBuffersInternal()
{
// blit framebuffer to back buffer
m_context->makeCurrent(this);
// make sure all paint operation have been processed
m_functions->glFlush();
// check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not
// OpenGL ES 2.0
if (m_functions_3_0) {
// if our framebuffer has multi-sampling, the resolve should be done automagically
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// blit all buffers including depth buffer for further rendering
m_functions_3_0->glBlitFramebuffer(0, 0, bufferSize().width(),
bufferSize().height(), 0, 0, bufferSize().width(),
bufferSize().height(), GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
} else {
// we must unbind the FBO here, so we can use its texture and bind the default back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, 0);
// now use its texture for drawing in the shader
// --> bind shader and draw textured quad here
// bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
// check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) {
qDebug() << "OpenGlOffscreenSurface::swapBuffersInternal() - OpenGL error" << error;
}
// now swap back buffer to front buffer
m_context->swapBuffers(this);
} // OpenGlOffscreenSurface::swapBuffersInternal
void OpenGlOffscreenSurface::recreateFBOAndPaintDevice()
{
if (m_context && ((m_fbo == nullptr) || (m_fbo->size() != bufferSize()))) {
m_context->makeCurrent(this);
// free old FBOs
if (m_fbo) {
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo) {
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
// create new frame buffer
// QOpenGLFramebufferObjectFormat format = QGLInfo::DefaultFramebufferFormat();
// format.setSamples(QGLInfo::HasMultisamplingSupport(m_context) ? 4 : 0);
QOpenGLFramebufferObjectFormat format;
format.setSamples(0);
m_fbo = new QOpenGLFramebufferObject(bufferSize(), format);
if (!m_fbo->isValid()) {
throw ("OpenGlOffscreenSurface::recreateFbo() - Failed to create background FBO!");
}
// clear framebuffer
m_fbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
m_fbo->release();
// if multi sampling is requested and supported we need a resolve FBO
if (format.samples() > 0) {
// create resolve framebuffer with only a color attachment
format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
format.setSamples(0);
m_resolvedFbo = new QOpenGLFramebufferObject(bufferSize(), format);
if (!m_resolvedFbo->isValid()) {
throw ("OpenGlOffscreenSurface::recreateFbo() - Failed to create resolve FBO!");
}
// clear resolve framebuffer
m_resolvedFbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT);
m_resolvedFbo->release();
}
}
// create paint device for painting with QPainter if needed
if (!m_paintDevice) {
m_paintDevice = new QOpenGLPaintDevice;
}
// update paint device size if needed
if (m_paintDevice->size() != bufferSize()) {
m_paintDevice->setSize(bufferSize());
}
} // OpenGlOffscreenSurface::recreateFBOAndPaintDevice
void OpenGlOffscreenSurface::initializeInternal()
{
if (!m_initialized.exchange(true)) {
// create OpenGL context. we set the format requested by the user (default:
// QWindow::requestedFormat())
m_context = new QOpenGLContext(this);
m_context->setFormat(format());
if (m_context->create()) {
m_context->makeCurrent(this);
// initialize the OpenGL 2.1 / ES 2.0 functions for this object
m_functions = m_context->functions();
m_functions->initializeOpenGLFunctions();
// try initializing the OpenGL 3.0 functions for this object
m_functions_3_0 = m_context->versionFunctions <QOpenGLFunctions_3_0>();
if (m_functions_3_0) {
m_functions_3_0->initializeOpenGLFunctions();
} else {
// if we do not have OpenGL 3.0 functions, glBlitFrameBuffer is not available, so we
// must do the blit
// using a shader and the framebuffer texture, so we need to create the shader
// here...
// --> allocate m_blitShader, a simple shader for drawing a textured quad
// --> build quad geometry, VBO, whatever
}
// now we have a context, create the FBO
recreateFBOAndPaintDevice();
} else {
m_initialized = false;
delete m_context;
m_context = nullptr;
throw ("Failed to create OpenGL context!");
}
}
} // OpenGlOffscreenSurface::initializeInternal
void OpenGlOffscreenSurface::update()
{
// only queue an update if there's not already an update pending
if (!m_updatePending.exchange(true)) {
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
}
}
void OpenGlOffscreenSurface::render()
{
std::lock_guard <std::mutex> locker(m_mutex);
// check if we need to initialize stuff
initializeInternal();
// check if we need to call the user initialization
// makeCurrent(); // TODO: may be makeCurrent() must be here, as noted for QOpenGLWidget.initializeGL()
if (!m_initializedGL) {
m_initializedGL = true;
initializeGL();
}
// make context current and bind framebuffer
makeCurrent();
bindFramebufferObject();
// call user paint function
paintGL();
doneCurrent();
// mark that we're done with updating
m_updatePending = false;
} // OpenGlOffscreenSurface::render
void OpenGlOffscreenSurface::exposeEvent(QExposeEvent* e)
{
// render window content if window is exposed
render();
} // OpenGlOffscreenSurface::exposeEvent
void OpenGlOffscreenSurface::resizeEvent(QResizeEvent* e)
{
// call base implementation
resize(e->size());
emit resized();
}
void OpenGlOffscreenSurface::resize(const QSize& newSize)
{
m_mutex.lock();
// make context current first
makeCurrent();
m_size = QSize(newSize);
// update FBO and paint device
recreateFBOAndPaintDevice();
m_mutex.unlock();
// call user-defined resize method
resizeGL(bufferSize().width(), bufferSize().height());
} // OpenGlOffscreenSurface::resize
void OpenGlOffscreenSurface::resize(
int w,
int h)
{
resize(QSize(w, h));
}
bool OpenGlOffscreenSurface::event(QEvent* event)
{
switch (event->type()) {
case QEvent::UpdateLater:
update();
return (true);
case QEvent::UpdateRequest:
render();
return (true);
default:
return (false);
} // switch
} // OpenGlOffscreenSurface::event
QSize OpenGlOffscreenSurface::bufferSize() const
{
return (m_size);
}
使用OpenGlOffscreenSurface
:
Header ExamplePaintSurface.h
:
#ifndef EXAMPLEPAINTSURFACE_H
#define EXAMPLEPAINTSURFACE_H
#include "OpenGlOffscreenSurface.h"
class ExamplePaintSurface
: public OpenGlOffscreenSurface
{
public:
explicit ExamplePaintSurface(
QScreen* targetScreen = nullptr,
const QSize& size = QSize (1, 1));
virtual ~ExamplePaintSurface() override;
protected:
virtual void initializeGL() override;
virtual void resizeGL(
int width,
int height) override;
virtual void paintGL() override;
};
#endif // EXAMPLEPAINTSURFACE_H
来源ExamplePaintSurface.cpp
:
#include "ExamplePaintSurface.h"
#include <QPainter>
ExamplePaintSurface::ExamplePaintSurface(
QScreen* targetScreen,
const QSize& size)
: OpenGlOffscreenSurface(targetScreen, size) {}
ExamplePaintSurface::~ExamplePaintSurface() {}
void ExamplePaintSurface::initializeGL() {}
void ExamplePaintSurface::resizeGL(int width, int height) {}
void ExamplePaintSurface::paintGL()
{
QPainter painter(getPaintDevice());
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.drawText(20, 40, "Test"); // <-- drawing here
painter.end();
}
来源main.cpp
:
#include <QApplication>
#include "ExamplePaintSurface.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ExamplePaintSurface paintSurface;
paintSurface.resize(300, 200);
paintSurface.render();
QImage image = paintSurface.grabFramebuffer();
image.save(QString("image.png"));
return a.exec();
}
带有 QOffscreenSurface 的部分示例来自 此处:
#include <QGuiApplication>
#include <QSurfaceFormat>
#include <QOpenGlContext>
#include <QOffscreenSurface>
#include <QOpenGLFunctions_4_3_Core>
#include <QDebug>
int main(int argc, char* argv[])
{
QGuiApplication a(argc, argv);
QSurfaceFormat surfaceFormat;
surfaceFormat.setMajorVersion(4);
surfaceFormat.setMinorVersion(3);
QOpenGLContext openGLContext;
openGLContext.setFormat(surfaceFormat);
openGLContext.create();
if(!openGLContext.isValid()) return -1;
QOffscreenSurface surface;
surface.setFormat(surfaceFormat);
surface.create();
if(!surface.isValid()) return -2;
openGLContext.makeCurrent(&surface);
QOpenGLFunctions_4_3_Core f;
if(!f.initializeOpenGLFunctions()) return -3;
qDebug() << QString::fromLatin1((const char*)f.glGetString(GL_VERSION));
return 0;
}
而 here 是一个关于它的答案:
For offscreen rendering, try rendering into a
QOpenGLFramebufferObject, which can be converted into a
QImage
, which in turn can easily be saved to disk.
For that however, you still need a surface to render onto (as required
by QOpenGLContext::makeCurrent()
), so your only choice is
indeed using a QWindow
or a QGLWidget
to get such a surface.
In Qt 5.1, there will be a new class called
QOffscreenSurface,
which will probably be most suitable for your usecase. You would use
QOffscreenSurface
to get a surface for your OpenGL
context, and then render into a FBO using
QOpenGLFramebufferObject
, and then call
QOpenGLFramebufferObject::toImage()
to get access to the
pixels.
我有一个隐藏的 QOpenGLWidget(Qt 5.4.2,不是 QGLWidget),我想基本上不断地执行 grab() 或 grabFramebuffer() 来获取它的内容(并将其写入磁盘)。小部件在可见时呈现良好,但在隐藏时则不会。如果我执行一个 show() 后跟一个 hide() 调用,它就可以工作。这看起来很奇怪,因为根据文档,QOpenGLWidget 在内部已经渲染到帧缓冲区。实现此目的的最简单方法是什么(如果可能,无需创建另一个帧缓冲区)?
能够使用 QOpenGLWidget 作为其视口并在其中使用自定义 OpenGL 绘制的 QGraphicsItems 捕获屏幕外 QGraphicsView 的奖励积分...
更新 2:corresponding bug in QOpenGLWidget seems to be fixed in Qt 5.10, so I suggest to simply use the class again. Although you might want to wait for this bug 也得到修复...
更新 1:添加了 3,使用自定义的最佳解决方案 QWindow-derived class
1 - QOpenGLWidget
如果隐藏的 QOpenGLWidget 确实分配了一个帧缓冲区(不确定是否会发生这种情况),仍然无法手动绑定它,因为您无法获取缓冲区 ID。此外 none 的必要函数 initializeGL()、resizeGL() 和 paintGL 被调用,并且 none 的函数 grab()、grabFramebuffer 和 render() 正常工作。这是(imo)将小部件绘制到屏幕外的解决方法。设置完所有必要的东西后直接调用 paintGL:
class GLWidget: public QOpenGLWidget
{
public:
GLWidget(QWidget * parent = nullptr);
private:
bool m_isInitialized = false;
QOpenGLFramebufferObject m_fbo = nullptr;
};
void GLWidget::drawOffscreen()
{
//the context should be valid. make sure it is current for painting
makeCurrent();
if (!m_isInitialized)
{
initializeGL();
resizeGL(width(), height());
}
if (!m_fbo || m_fbo->width() != width() || m_fbo->height() != height())
{
//allocate additional? FBO for rendering or resize it if widget size changed
delete m_fbo;
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
m_fbo = new QOpenGLFramebufferObject(width(), height(), format);
resizeGL(width(), height());
}
//#1 DOES NOT WORK: bind FBO and render() widget
m_fbo->bind();
QOpenGLPaintDevice fboPaintDev(width(), height());
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
render(&painter);
painter.end();
//You could now grab the content of the framebuffer we've rendered to
QImage image1 = m_fbo->toImage();
image1.save(QString("fb1.png"));
m_fbo->release();
//#1 --------------------------------------------------------------
//#2 WORKS: bind FBO and render stuff with paintGL() call
m_fbo->bind();
paintGL();
//You could now grab the content of the framebuffer we've rendered to
QImage image2 = m_fbo->toImage();
image2.save(QString("fb2.png"));
m_fbo->release();
//#2 --------------------------------------------------------------
//bind default framebuffer again. not sure if this necessary
//and isn't supposed to use defaultFramebuffer()...
m_fbo->bindDefault();
doneCurrent();
}
void GLWidget::paintGL()
{
//When doing mixed QPainter/OpenGL rendering make sure to use a QOpenGLPaintDevice, otherwise only OpenGL content is visible!
//I'm not sure why, because according to the docs (http://doc.qt.io/qt-5/topics-graphics.html) this is supposed to be the same...
QOpenGLPaintDevice fboPaintDev(width(), height());
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
//This is what you'd use (and what would work) if the widget was visible
//QPainter painter;
//painter.begin(this);
//now start OpenGL painting
painter.beginNativePainting();
glClearColor(0.5f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
...
painter.endNativePainting();
//draw non-OpenGL stuff with QPainter
painter.drawText(20, 40, "Foo");
...
painter.end();
}
2 - 带有 QOpenGLWidget 视口的 QGraphicsView
当您为它提供 QOpenGLPaintDevice:
MainWindow::MainWindow()
{
scene = new QGraphicsScene;
hiddenView = new QGraphicsView(scene);
hiddenGLWidget = new QOpenGLWidget;
hiddenView->setViewport(hiddenGLWidget);
//hiddenView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
//hiddenView->show();
}
void MainWindow::screenshot()
{
//try regular grab functions
QPixmap pixmap1 = hiddenView->grab(); //image with scrollbars, no OpenGL content
pixmap1.save("bla1.png");
QPixmap pixmap2 = hiddenGLWidget->grab(); //produces an empty image
pixmap2.save("bla2.png");
//try grabbing only the QOpenGLWidget framebuffer
QImage image1 = hiddenGLWidget->grabFramebuffer(); //null image
image1.save("bla3.png");
//WORKS: render via FBO
hiddenGLWidget->makeCurrent();
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObject * fbo = new QOpenGLFramebufferObject(hiddenView->width(), hiddenView->height(), format);
fbo->bind();
QOpenGLPaintDevice fboPaintDev(hiddenView->width(), hiddenView->height());
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
hiddenView->render(&painter); //WORKS and captures mixed OpenGL and non-OpenGL QGraphicsitems
//hiddenView->repaint(); //does not work
//hiddenView->scene()->render(&painter); //does not work
//hiddenGLWidget->paintGL(); //might work. can not call, protected
//hiddenGLWidget->render(&painter); //does not work
//hiddenGLWidget->repaint(); //does not work
painter.end();
QImage image2 = fbo->toImage();
image2.save("bla4.png");
fbo->release();
delete fbo;
}
3 - 如何从隐藏的 QOpenGLWidget 渲染和抓取图像
更好的整体解决方案是使用具有 QSurface::OpenGLSurface 类型的自定义 QWindow。创建一个额外的 QOpenGLContext,一个您将绘制到的额外背景 QOpenGLFramebufferObject,以及一个 QOpenGLShaderProgram 以将帧缓冲区 blit 到后台缓冲区。如果你想要多重采样,你可能还需要一个解析 QOpenGLFramebufferObject,将多重采样帧缓冲区转换为 non-multisampled 帧缓冲区。
class接口可以类似于QOpenGLWidget(为用户虚拟initializeGL()、resizeGL()、paintGL())。重新实现 exposeEvent()、resizeEvent() 和 event()(您可能也需要重新实现 metric())。
semi-complete 实现:
Header:
#pragma once
#include <QtCore/QObject>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtGui/QPaintEvent>
#include <QtGui/QResizeEvent>
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLFunctions_3_0>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QSurfaceFormat>
#include <QtWidgets/QWidget>
#include <atomic>
#include <mutex>
class MyGLWindow : public QWindow
{
Q_OBJECT
public:
/// @brief Constructor. Creates a render window.
/// @param targetScreen Target screen.
/// this is because before the FBO and off-screen surface haven't been created.
/// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen surface.
explicit MyGLWindow(QScreen * targetScreen = nullptr);
/// @brief Constructor. Creates a render window.
/// @param parent Parent window.
/// this is because before the FBO and off-screen surface haven't been created.
/// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen surface.
explicit MyGLWindow(QWindow * parent);
/// @brief Destructor.
virtual ~MyGLWindow();
/// @brief Create a container widget for this window.
/// @param parent Parent widget.
/// @return Returns a container widget for the window.
QWidget * createWidget(QWidget * parent = nullptr);
/// @brief Check if the window is initialized and can be used for rendering.
/// @return Returns true if context, surface and FBO have been set up to start rendering.
bool isValid() const;
/// @brief Return the context used in this window.
/// @return The context used in this window or nullptr if it hasn't been created yet.
QOpenGLContext * context() const;
/// @brief Return the OpenGL function object that can be used the issue OpenGL commands.
/// @return The functions for the context or nullptr if it the context hasn't been created yet.
QOpenGLFunctions * functions() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created yet.
/// @note This changes on every resize!
GLuint framebufferObjectHandle() const;
/// @brief Return the OpenGL off-screen frame buffer object.
/// @return The OpenGL off-screen frame buffer object or nullptr if no FBO has been created yet.
/// @note This changes on every resize!
const QOpenGLFramebufferObject * getFramebufferObject() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created yet.
void bindFramebufferObject();
/// @brief Return the current contents of the FBO.
/// @return FBO content as 32bit QImage. You might need to swap RGBA to BGRA or vice-versa.
QImage grabFramebuffer();
/// @brief Makes the OpenGL context current for rendering.
/// @note Make sure to bindFramebufferObject() if you want to render to this widgets FBO.
void makeCurrent();
/// @brief Release the OpenGL context.
void doneCurrent();
/// @brief Copy content of framebuffer to back buffer and swap buffers if the surface is double-buffered.
/// If the surface is not double-buffered, the frame buffer content is blitted to the front buffer.
/// If the window is not exposed, only the OpenGL pipeline is glFlush()ed so the framebuffer can be read back.
void swapBuffers();
public slots:
/// @brief Lazy update routine like QWidget::update().
void update();
/// @brief Immediately render the widget contents to framebuffer.
void render();
signals:
/// @brief Emitted when swapBuffers() was called and bufferswapping is done.
void frameSwapped();
/// @brief Emitted after a resizeEvent().
void resized();
protected:
virtual void exposeEvent(QExposeEvent *e) override;
virtual void resizeEvent(QResizeEvent *e) override;
virtual bool event(QEvent *e) override;
// virtual int metric(QPaintDevice::PaintDeviceMetric metric) const override;
/// @brief Called exactly once when the window is first exposed OR render() is called when the widget is invisible.
/// @note After this the off-screen surface and FBO are available.
virtual void initializeGL() = 0;
/// @brief Called whenever the window size changes.
/// @param width New window width.
/// @param height New window height.
virtual void resizeGL(int width, int height) = 0;
/// @brief Called whenever the window needs to repaint itself. Override to draw OpenGL content.
/// When this function is called, the context is already current and the correct framebuffer is bound.
virtual void paintGL() = 0;
// /// @brief Called whenever the window needs to repaint itself. Override to draw QPainter content.
// /// @brief This is called AFTER paintGL()! Only needed when painting using a QPainter.
// virtual void paintEvent(QPainter & painter) = 0;
private:
Q_DISABLE_COPY(QGLWindow)
/// @brief Initialize the window.
void initializeInternal();
/// @brief Internal method that does the actual swap work, NOT using a mutex.
void swapBuffersInternal();
/// @brief Internal method that checks state and makes the context current, NOT using a mutex.
void makeCurrentInternal();
/// @brief Internal method to grab content of a specific framebuffer.
QImage grabFramebufferInternal(QOpenGLFramebufferObject * fbo);
/// @brief (Re-)allocate FBO and paint device if needed due to size changes etc.
void recreateFBOAndPaintDevice();
/// @brief False before the window was first exposed OR render() was called.
std::atomic_bool m_initialized;
/// @brief False before the overridden initializeGL() was first called.
bool m_initializedGL = false;
/// @brief True when currently a window update is pending.
std::atomic_bool m_updatePending;
/// @brief Mutex making sure not grabbing while drawing etc.
std::mutex m_mutex;
/// @brief OpenGL render context.
QOpenGLContext * m_context = nullptr;
/// @brief The OpenGL 2.1 / ES 2.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions * m_functions = nullptr;
/// @brief The OpenGL 3.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions_3_0 * m_functions_3_0 = nullptr;
/// @brief OpenGL paint device for painting with a QPainter.
QOpenGLPaintDevice * m_paintDevice = nullptr;
/// @brief Background FBO for off-screen rendering when the window is not exposed.
QOpenGLFramebufferObject * m_fbo = nullptr;
/// @brief Background FBO resolving a multi sampling frame buffer in m_fbo to a frame buffer
/// that can be grabbed to a QImage.
QOpenGLFramebufferObject * m_resolvedFbo = nullptr;
/// @brief Shader used for blitting m_fbo to screen if glBlitFrameBuffer is not available.
QOpenGLShaderProgram * m_blitShader;
};
来源:
#include "MyGLWindow.h"
#include <QtCore/QCoreApplication>
#include <QtGui/QPainter>
MyGLWindow::MyGLWindow(QScreen * targetScreen)
: QWindow(targetScreen)
{
//Set Qt::Widget flag to make sure the window resizes properly the first time
//when used as a widget via MyGLWindow::createWidget()!
setFlags(Qt::Widget);
setSurfaceType(QSurface::OpenGLSurface);
setFormat(QGLInfo::DefaultSurfaceFormat());
m_initialized = false;
m_updatePending = false;
create();
initializeInternal();
}
MyGLWindow::MyGLWindow(QWindow * parent)
: QWindow(parent)
{
//Set Qt::Widget flag to make sure the window resizes properly the first time
//when used as a widget via MyGLWindow::createWidget()!
setFlags(Qt::Widget);
setSurfaceType(QSurface::OpenGLSurface);
setFormat(QGLInfo::DefaultSurfaceFormat());
m_initialized = false;
m_updatePending = false;
create();
initializeInternal();
}
MyGLWindow::~MyGLWindow()
{
//to delete the FBOs we first need to make the context current
m_context->makeCurrent(this);
//destroy framebuffer objects
if (m_fbo)
{
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo)
{
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
//destroy shader
delete m_blitShader;
m_blitShader = nullptr;
//free context
m_context->doneCurrent();
delete m_context;
m_context = nullptr;
//free paint device
delete m_paintDevice;
m_paintDevice = nullptr;
m_initialized = false;
m_updatePending = false;
}
QWidget * MyGLWindow::createWidget(QWidget * parent)
{
QWidget * container = QWidget::createWindowContainer(this, parent);
return container;
}
QOpenGLContext * MyGLWindow::context() const
{
return m_context;
}
QOpenGLFunctions * MyGLWindow::functions() const
{
return m_functions;
}
GLuint MyGLWindow::framebufferObjectHandle() const
{
return m_fbo ? m_fbo->handle() : 0;
}
const QOpenGLFramebufferObject * MyGLWindow::getFramebufferObject() const
{
return m_fbo;
}
void MyGLWindow::bindFramebufferObject()
{
if (m_fbo)
{
m_fbo->bind();
}
else
{
QOpenGLFramebufferObject::bindDefault();
}
}
bool MyGLWindow::isValid() const
{
return (m_initialized && m_context && m_fbo);
}
void MyGLWindow::makeCurrent()
{
makeCurrentInternal();
}
void MyGLWindow::makeCurrentInternal()
{
if (isValid())
{
m_context->makeCurrent(this);
}
else
{
throw("MyGLWindow::makeCurrent() - Window not yet properly initialized!");
}
}
void MyGLWindow::doneCurrent()
{
if (m_context)
{
m_context->doneCurrent();
}
}
QImage MyGLWindow::grabFramebuffer()
{
std::lock_guard<std::mutex> locker(m_mutex);
makeCurrentInternal();
//blit framebuffer to resolve framebuffer first if needed
if (m_fbo->format().samples() > 0)
{
//check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0
if (m_functions_3_0)
{
//only blit the color buffer attachment
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolvedFbo->handle());
m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(),
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
else
{
//we must unbind the FBO here, so we can use its texture and bind the default back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFbo->handle());
//now use its texture for drawing in the shader
--> bind shader and draw textured quad here
//bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
//check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR)
{
qDebug() << "MyGLWindow::grabFramebuffer() - OpenGL error" << error;
}
//now grab from resolve FBO
return grabFramebufferInternal(m_resolvedFbo);
}
else
{
//no multi-sampling. grab directly from FBO
return grabFramebufferInternal(m_fbo);
}
}
QImage MyGLWindow::grabFramebufferInternal(QOpenGLFramebufferObject * fbo)
{
QImage image;
//bind framebuffer first
m_functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
if (m_functions_3_0)
{
m_functions_3_0->glReadBuffer(GL_COLOR_ATTACHMENT0);
}
GLenum internalFormat = fbo->format().internalTextureFormat();
bool hasAlpha = internalFormat == GL_RGBA || internalFormat == GL_BGRA || internalFormat == GL_RGBA8;
if (internalFormat == GL_BGRA)
{
image = QImage(fbo->size(), hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
}
else if (internalFormat == GL_RGBA || internalFormat == GL_RGBA8)
{
image = QImage(fbo->size(), hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888);
m_functions->glReadPixels(0, 0, fbo->size().width(), fbo->size().height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
}
else
{
qDebug() << "MyGLWindow::grabFramebuffer() - Unsupported framebuffer format" << internalFormat << "!";
}
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
return image.mirrored();
}
void MyGLWindow::swapBuffers()
{
swapBuffersInternal();
emit frameSwapped();
}
void MyGLWindow::swapBuffersInternal()
{
if (isExposed() && isVisible())
{
//blit framebuffer to back buffer
m_context->makeCurrent(this);
//make sure all paint operation have been processed
m_functions->glFlush();
//check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not OpenGL ES 2.0
if (m_functions_3_0)
{
//if our framebuffer has multi-sampling, the resolve should be done automagically
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
//blit all buffers including depth buffer for further rendering
m_functions_3_0->glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(),
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
else
{
//we must unbind the FBO here, so we can use its texture and bind the default back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, 0);
//now use its texture for drawing in the shader
--> bind shader and draw textured quad here
//bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
//check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR)
{
qDebug() << "MyGLWindow::swapBuffersInternal() - OpenGL error" << error;
}
//now swap back buffer to front buffer
m_context->swapBuffers(this);
}
else
{
//not visible. only flush the pipeline so we can possibly grab the FBO later
m_context->makeCurrent(this);
m_functions->glFlush();
}
}
void MyGLWindow::recreateFBOAndPaintDevice()
{
const QSize deviceSize = size() * devicePixelRatio();
if (m_context && (m_fbo == nullptr || m_fbo->size() != deviceSize))
{
m_context->makeCurrent(this);
//free old FBOs
if (m_fbo)
{
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo)
{
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
//create new frame buffer
QOpenGLFramebufferObjectFormat format = QGLInfo::DefaultFramebufferFormat();
format.setSamples(QGLInfo::HasMultisamplingSupport(m_context) ? 4 : 0);
m_fbo = new QOpenGLFramebufferObject(deviceSize, format);
if (!m_fbo->isValid())
{
throw("MyGLWindow::recreateFbo() - Failed to create background FBO!");
}
//clear framebuffer
m_fbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
m_fbo->release();
//if multi sampling is requested and supported we need a resolve FBO
if (format.samples() > 0)
{
//create resolve framebuffer with only a color attachment
format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
format.setSamples(0);
m_resolvedFbo = new QOpenGLFramebufferObject(deviceSize, format);
if (!m_resolvedFbo->isValid())
{
throw("MyGLWindow::recreateFbo() - Failed to create resolve FBO!");
}
//clear resolve framebuffer
m_resolvedFbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT);
m_resolvedFbo->release();
}
}
//create paint device for painting with QPainter if needed
if (!m_paintDevice)
{
m_paintDevice = new QOpenGLPaintDevice;
}
//update paint device size if needed
if (m_paintDevice->size() != deviceSize)
{
m_paintDevice->setDevicePixelRatio(devicePixelRatio());
m_paintDevice->setSize(deviceSize);
}
}
void MyGLWindow::initializeInternal()
{
if (!m_initialized.exchange(true))
{
//create OpenGL context. we set the format requested by the user (default: QWindow::requestedFormat())
m_context = new QOpenGLContext(this);
m_context->setFormat(format());
if (m_context->create())
{
m_context->makeCurrent(this);
//initialize the OpenGL 2.1 / ES 2.0 functions for this object
m_functions = m_context->functions();
m_functions->initializeOpenGLFunctions();
//try initializing the OpenGL 3.0 functions for this object
m_functions_3_0 = m_context->versionFunctions<QOpenGLFunctions_3_0>();
if (m_functions_3_0)
{
m_functions_3_0->initializeOpenGLFunctions();
}
else
{
//if we do not have OpenGL 3.0 functions, glBlitFrameBuffer is not available, so we must do the blit
//using a shader and the framebuffer texture, so we need to create the shader here...
--> allocate m_blitShader, a simple shader for drawing a textured quad
--> build quad geometry, VBO, whatever
}
//now we have a context, create the FBO
recreateFBOAndPaintDevice();
}
else
{
m_initialized = false;
delete m_context;
m_context = nullptr;
throw("Failed to create OpenGL context!");
}
}
}
void MyGLWindow::update()
{
//only queue an update if there's not already an update pending
if (!m_updatePending.exchange(true))
{
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
}
}
void MyGLWindow::render()
{
std::lock_guard<std::mutex> locker(m_mutex);
//check if we need to initialize stuff
initializeInternal();
//check if we need to call the user initialization
if (!m_initializedGL)
{
m_initializedGL = true;
initializeGL();
}
//make context current and bind framebuffer
makeCurrent();
bindFramebufferObject();
//call user paint function
paintGL();
doneCurrent();
//mark that we're done with updating
m_updatePending = false;
}
void MyGLWindow::exposeEvent(QExposeEvent * e)
{
//call base implementation
QWindow::exposeEvent(e);
//render window content if window is exposed
if (isExposed()/* && isVisible()*/)
{
render();
}
}
void MyGLWindow::resizeEvent(QResizeEvent *e)
{
//call base implementation
QWindow::resizeEvent(e);
m_mutex.lock();
//make context current first
makeCurrent();
//update FBO and paint device
recreateFBOAndPaintDevice();
m_mutex.unlock();
//call user-defined resize method
resizeGL(e->size().width(), e->size().height());
emit resized();
}
bool MyGLWindow::event(QEvent *event)
{
switch (event->type())
{
case QEvent::UpdateLater:
update();
return true;
case QEvent::UpdateRequest:
render();
return true;
default:
return QWindow::event(event);
}
}
对于离屏渲染我们也可以使用QOffscreenSurface。
这里是基于 Bim 的 MyGLWindow class 工作的(并非所有都经过测试):
Header OpenGlOffscreenSurface.h
:
#ifndef OPENGLOFFSCREENSURFACE_H
#define OPENGLOFFSCREENSURFACE_H
#pragma once
#include <QtCore/QObject>
#include <QtGui/QScreen>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QPaintEvent>
#include <QtGui/QResizeEvent>
#include <QtGui/QOpenGLPaintDevice>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLFunctions_3_0>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QSurfaceFormat>
#include <QtWidgets/QWidget>
#include <QOpenGLShaderProgram>
#include <atomic>
#include <mutex>
class OpenGlOffscreenSurface
: public QOffscreenSurface
{
Q_OBJECT
public:
/// @brief Constructor. Creates a render window.
/// @param targetScreen Target screen.
/// @param size Initial size of a surface buffer.
/// this is because before the FBO and off-screen surface haven't been created.
/// By default this uses the QWindow::requestedFormat() for OpenGL context and off-screen
/// surface.
explicit OpenGlOffscreenSurface(
QScreen* targetScreen = nullptr,
const QSize& size = QSize (1, 1));
/// @brief Destructor.
virtual ~OpenGlOffscreenSurface();
/// @brief Check if the window is initialized and can be used for rendering.
/// @return Returns true if context, surface and FBO have been set up to start rendering.
bool isValid() const;
/// @brief Return the context used in this window.
/// @return The context used in this window or nullptr if it hasn't been created yet.
QOpenGLContext* context() const;
/// @brief Return the OpenGL function object that can be used the issue OpenGL commands.
/// @return The functions for the context or nullptr if it the context hasn't been created yet.
QOpenGLFunctions* functions() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created
/// yet.
/// @note This changes on every resize!
GLuint framebufferObjectHandle() const;
/// @brief Return the OpenGL off-screen frame buffer object.
/// @return The OpenGL off-screen frame buffer object or nullptr if no FBO has been created yet.
/// @note This changes on every resize!
const QOpenGLFramebufferObject* getFramebufferObject() const;
/// @brief Return the QPaintDevice for paint into it.
QPaintDevice* getPaintDevice() const;
/// @brief Return the OpenGL off-screen frame buffer object identifier.
/// @return The OpenGL off-screen frame buffer object identifier or 0 if no FBO has been created
/// yet.
void bindFramebufferObject();
/// @brief Return the current contents of the FBO.
/// @return FBO content as 32bit QImage. You might need to swap RGBA to BGRA or vice-versa.
QImage grabFramebuffer();
/// @brief Makes the OpenGL context current for rendering.
/// @note Make sure to bindFramebufferObject() if you want to render to this widgets FBO.
void makeCurrent();
/// @brief Release the OpenGL context.
void doneCurrent();
/// @brief Copy content of framebuffer to back buffer and swap buffers if the surface is
/// double-buffered.
/// If the surface is not double-buffered, the frame buffer content is blitted to the front
/// buffer.
/// If the window is not exposed, only the OpenGL pipeline is glFlush()ed so the framebuffer can
/// be read back.
void swapBuffers();
/// @brief Use bufferSize() instead size() for get a size of a surface buffer. We can't override size() as it is not virtual.
QSize bufferSize() const;
/// @brief Resize surface buffer to newSize.
void resize(const QSize& newSize);
/// @brief Resize surface buffer to size with width w and height h.
/// @param w Width.
/// @param h Height.
void resize(int w, int h);
public slots:
/// @brief Lazy update routine like QWidget::update().
void update();
/// @brief Immediately render the widget contents to framebuffer.
void render();
signals:
/// @brief Emitted when swapBuffers() was called and bufferswapping is done.
void frameSwapped();
/// @brief Emitted after a resizeEvent().
void resized();
protected:
virtual void exposeEvent(QExposeEvent* e);
virtual void resizeEvent(QResizeEvent* e);
virtual bool event(QEvent* e) override;
// virtual int metric(QPaintDevice::PaintDeviceMetric metric) const override;
/// @brief Called exactly once when the window is first exposed OR render() is called when the
/// widget is invisible.
/// @note After this the off-screen surface and FBO are available.
virtual void initializeGL() = 0;
/// @brief Called whenever the window size changes.
/// @param width New window width.
/// @param height New window height.
virtual void resizeGL(
int width,
int height) = 0;
/// @brief Called whenever the window needs to repaint itself. Override to draw OpenGL content.
/// When this function is called, the context is already current and the correct framebuffer is
/// bound.
virtual void paintGL() = 0;
// /// @brief Called whenever the window needs to repaint itself. Override to draw QPainter
// content.
// /// @brief This is called AFTER paintGL()! Only needed when painting using a QPainter.
// virtual void paintEvent(QPainter & painter) = 0;
private:
Q_DISABLE_COPY(OpenGlOffscreenSurface)
/// @brief Initialize the window.
void initializeInternal();
/// @brief Internal method that does the actual swap work, NOT using a mutex.
void swapBuffersInternal();
/// @brief Internal method that checks state and makes the context current, NOT using a mutex.
void makeCurrentInternal();
/// @brief Internal method to grab content of a specific framebuffer.
QImage grabFramebufferInternal(QOpenGLFramebufferObject* fbo);
/// @brief (Re-)allocate FBO and paint device if needed due to size changes etc.
void recreateFBOAndPaintDevice();
/// @brief False before the window was first exposed OR render() was called.
std::atomic_bool m_initialized;
/// @brief False before the overridden initializeGL() was first called.
bool m_initializedGL = false;
/// @brief True when currently a window update is pending.
std::atomic_bool m_updatePending;
/// @brief Mutex making sure not grabbing while drawing etc.
std::mutex m_mutex;
/// @brief OpenGL render context.
QOpenGLContext* m_context = nullptr;
/// @brief The OpenGL 2.1 / ES 2.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions* m_functions = nullptr;
/// @brief The OpenGL 3.0 function object that can be used the issue OpenGL commands.
QOpenGLFunctions_3_0* m_functions_3_0 = nullptr;
/// @brief OpenGL paint device for painting with a QPainter.
QOpenGLPaintDevice* m_paintDevice = nullptr;
/// @brief Background FBO for off-screen rendering when the window is not exposed.
QOpenGLFramebufferObject* m_fbo = nullptr;
/// @brief Background FBO resolving a multi sampling frame buffer in m_fbo to a frame buffer
/// that can be grabbed to a QImage.
QOpenGLFramebufferObject* m_resolvedFbo = nullptr;
/// @brief Shader used for blitting m_fbo to screen if glBlitFrameBuffer is not available.
QOpenGLShaderProgram* m_blitShader;
QSize m_size;
};
#endif // OPENGLOFFSCREENSURFACE_H
来源OpenGlOffscreenSurface.cpp
:
#include "OpenGlOffscreenSurface.h"
#include <QtCore/QCoreApplication>
#include <QtGui/QPainter>
OpenGlOffscreenSurface::OpenGlOffscreenSurface(
QScreen* targetScreen,
const QSize& size)
: QOffscreenSurface(targetScreen)
, m_size(size)
{
setFormat(QSurfaceFormat::defaultFormat());
m_initialized = false;
m_updatePending = false;
create(); // Some platforms require this function to be called on the main (GUI) thread
initializeInternal();
}
OpenGlOffscreenSurface::~OpenGlOffscreenSurface()
{
// to delete the FBOs we first need to make the context current
m_context->makeCurrent(this);
// destroy framebuffer objects
if (m_fbo) {
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo) {
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
// destroy shader
delete m_blitShader;
m_blitShader = nullptr;
// free context
m_context->doneCurrent();
delete m_context;
m_context = nullptr;
// free paint device
delete m_paintDevice;
m_paintDevice = nullptr;
m_initialized = false;
m_updatePending = false;
destroy();
}
QOpenGLContext* OpenGlOffscreenSurface::context() const
{
return (m_context);
}
QOpenGLFunctions* OpenGlOffscreenSurface::functions() const
{
return (m_functions);
}
GLuint OpenGlOffscreenSurface::framebufferObjectHandle() const
{
return (m_fbo ? m_fbo->handle() : 0);
}
const QOpenGLFramebufferObject* OpenGlOffscreenSurface::getFramebufferObject() const
{
return (m_fbo);
}
QPaintDevice* OpenGlOffscreenSurface::getPaintDevice() const
{
return (m_paintDevice);
}
void OpenGlOffscreenSurface::bindFramebufferObject()
{
if (m_fbo) {
m_fbo->bind();
} else {
QOpenGLFramebufferObject::bindDefault();
}
}
bool OpenGlOffscreenSurface::isValid() const
{
return (m_initialized && m_context && m_fbo);
}
void OpenGlOffscreenSurface::makeCurrent()
{
makeCurrentInternal();
}
void OpenGlOffscreenSurface::makeCurrentInternal()
{
if (isValid()) {
m_context->makeCurrent(this);
} else {
throw ("OpenGlOffscreenSurface::makeCurrent() - Window not yet properly initialized!");
}
}
void OpenGlOffscreenSurface::doneCurrent()
{
if (m_context) {
m_context->doneCurrent();
}
}
QImage OpenGlOffscreenSurface::grabFramebuffer()
{
std::lock_guard <std::mutex> locker(m_mutex);
makeCurrentInternal();
// blit framebuffer to resolve framebuffer first if needed
if (m_fbo->format().samples() > 0) {
// check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not
// OpenGL ES 2.0
if (m_functions_3_0) {
// only blit the color buffer attachment
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolvedFbo->handle());
m_functions_3_0->glBlitFramebuffer(0, 0, bufferSize().width(),
bufferSize().height(), 0, 0, bufferSize().width(),
bufferSize().height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, 0);
} else {
// we must unbind the FBO here, so we can use its texture and bind the default
// back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_resolvedFbo->handle());
// now use its texture for drawing in the shader
// --> bind shader and draw textured quad here
// bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
// check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) {
qDebug() << "OpenGlOffscreenSurface::grabFramebuffer() - OpenGL error" << error;
}
// now grab from resolve FBO
return (grabFramebufferInternal(m_resolvedFbo));
} else {
// no multi-sampling. grab directly from FBO
return (grabFramebufferInternal(m_fbo));
}
} // OpenGlOffscreenSurface::grabFramebuffer
QImage OpenGlOffscreenSurface::grabFramebufferInternal(QOpenGLFramebufferObject* fbo)
{
QImage image;
// bind framebuffer first
m_functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
if (m_functions_3_0) {
m_functions_3_0->glReadBuffer(GL_COLOR_ATTACHMENT0);
}
GLenum internalFormat = fbo->format().internalTextureFormat();
bool hasAlpha = internalFormat == GL_RGBA || internalFormat == GL_BGRA
|| internalFormat == GL_RGBA8;
if (internalFormat == GL_BGRA) {
image = QImage(fbo->size(), hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
m_functions->glReadPixels(0, 0, fbo->size().width(),
fbo->size().height(), GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
} else if ((internalFormat == GL_RGBA) || (internalFormat == GL_RGBA8)) {
image = QImage(fbo->size(), hasAlpha ? QImage::Format_RGBA8888 : QImage::Format_RGBX8888);
m_functions->glReadPixels(0, 0, fbo->size().width(),
fbo->size().height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
} else {
qDebug() << "OpenGlOffscreenSurface::grabFramebuffer() - Unsupported framebuffer format"
<< internalFormat << "!";
}
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
return (image.mirrored());
} // OpenGlOffscreenSurface::grabFramebufferInternal
void OpenGlOffscreenSurface::swapBuffers()
{
swapBuffersInternal();
emit frameSwapped();
}
void OpenGlOffscreenSurface::swapBuffersInternal()
{
// blit framebuffer to back buffer
m_context->makeCurrent(this);
// make sure all paint operation have been processed
m_functions->glFlush();
// check if we have glFrameBufferBlit support. this is true for desktop OpenGL 3.0+, but not
// OpenGL ES 2.0
if (m_functions_3_0) {
// if our framebuffer has multi-sampling, the resolve should be done automagically
m_functions_3_0->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo->handle());
m_functions_3_0->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// blit all buffers including depth buffer for further rendering
m_functions_3_0->glBlitFramebuffer(0, 0, bufferSize().width(),
bufferSize().height(), 0, 0, bufferSize().width(),
bufferSize().height(), GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
GL_NEAREST);
m_functions_3_0->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
} else {
// we must unbind the FBO here, so we can use its texture and bind the default back-buffer
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, 0);
// now use its texture for drawing in the shader
// --> bind shader and draw textured quad here
// bind regular FBO again
m_functions->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
}
// check if OpenGL errors happened
if (GLenum error = m_functions->glGetError() != GL_NO_ERROR) {
qDebug() << "OpenGlOffscreenSurface::swapBuffersInternal() - OpenGL error" << error;
}
// now swap back buffer to front buffer
m_context->swapBuffers(this);
} // OpenGlOffscreenSurface::swapBuffersInternal
void OpenGlOffscreenSurface::recreateFBOAndPaintDevice()
{
if (m_context && ((m_fbo == nullptr) || (m_fbo->size() != bufferSize()))) {
m_context->makeCurrent(this);
// free old FBOs
if (m_fbo) {
m_fbo->release();
delete m_fbo;
m_fbo = nullptr;
}
if (m_resolvedFbo) {
m_resolvedFbo->release();
delete m_resolvedFbo;
m_resolvedFbo = nullptr;
}
// create new frame buffer
// QOpenGLFramebufferObjectFormat format = QGLInfo::DefaultFramebufferFormat();
// format.setSamples(QGLInfo::HasMultisamplingSupport(m_context) ? 4 : 0);
QOpenGLFramebufferObjectFormat format;
format.setSamples(0);
m_fbo = new QOpenGLFramebufferObject(bufferSize(), format);
if (!m_fbo->isValid()) {
throw ("OpenGlOffscreenSurface::recreateFbo() - Failed to create background FBO!");
}
// clear framebuffer
m_fbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
m_fbo->release();
// if multi sampling is requested and supported we need a resolve FBO
if (format.samples() > 0) {
// create resolve framebuffer with only a color attachment
format.setAttachment(QOpenGLFramebufferObject::NoAttachment);
format.setSamples(0);
m_resolvedFbo = new QOpenGLFramebufferObject(bufferSize(), format);
if (!m_resolvedFbo->isValid()) {
throw ("OpenGlOffscreenSurface::recreateFbo() - Failed to create resolve FBO!");
}
// clear resolve framebuffer
m_resolvedFbo->bind();
m_functions->glClear(GL_COLOR_BUFFER_BIT);
m_resolvedFbo->release();
}
}
// create paint device for painting with QPainter if needed
if (!m_paintDevice) {
m_paintDevice = new QOpenGLPaintDevice;
}
// update paint device size if needed
if (m_paintDevice->size() != bufferSize()) {
m_paintDevice->setSize(bufferSize());
}
} // OpenGlOffscreenSurface::recreateFBOAndPaintDevice
void OpenGlOffscreenSurface::initializeInternal()
{
if (!m_initialized.exchange(true)) {
// create OpenGL context. we set the format requested by the user (default:
// QWindow::requestedFormat())
m_context = new QOpenGLContext(this);
m_context->setFormat(format());
if (m_context->create()) {
m_context->makeCurrent(this);
// initialize the OpenGL 2.1 / ES 2.0 functions for this object
m_functions = m_context->functions();
m_functions->initializeOpenGLFunctions();
// try initializing the OpenGL 3.0 functions for this object
m_functions_3_0 = m_context->versionFunctions <QOpenGLFunctions_3_0>();
if (m_functions_3_0) {
m_functions_3_0->initializeOpenGLFunctions();
} else {
// if we do not have OpenGL 3.0 functions, glBlitFrameBuffer is not available, so we
// must do the blit
// using a shader and the framebuffer texture, so we need to create the shader
// here...
// --> allocate m_blitShader, a simple shader for drawing a textured quad
// --> build quad geometry, VBO, whatever
}
// now we have a context, create the FBO
recreateFBOAndPaintDevice();
} else {
m_initialized = false;
delete m_context;
m_context = nullptr;
throw ("Failed to create OpenGL context!");
}
}
} // OpenGlOffscreenSurface::initializeInternal
void OpenGlOffscreenSurface::update()
{
// only queue an update if there's not already an update pending
if (!m_updatePending.exchange(true)) {
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
}
}
void OpenGlOffscreenSurface::render()
{
std::lock_guard <std::mutex> locker(m_mutex);
// check if we need to initialize stuff
initializeInternal();
// check if we need to call the user initialization
// makeCurrent(); // TODO: may be makeCurrent() must be here, as noted for QOpenGLWidget.initializeGL()
if (!m_initializedGL) {
m_initializedGL = true;
initializeGL();
}
// make context current and bind framebuffer
makeCurrent();
bindFramebufferObject();
// call user paint function
paintGL();
doneCurrent();
// mark that we're done with updating
m_updatePending = false;
} // OpenGlOffscreenSurface::render
void OpenGlOffscreenSurface::exposeEvent(QExposeEvent* e)
{
// render window content if window is exposed
render();
} // OpenGlOffscreenSurface::exposeEvent
void OpenGlOffscreenSurface::resizeEvent(QResizeEvent* e)
{
// call base implementation
resize(e->size());
emit resized();
}
void OpenGlOffscreenSurface::resize(const QSize& newSize)
{
m_mutex.lock();
// make context current first
makeCurrent();
m_size = QSize(newSize);
// update FBO and paint device
recreateFBOAndPaintDevice();
m_mutex.unlock();
// call user-defined resize method
resizeGL(bufferSize().width(), bufferSize().height());
} // OpenGlOffscreenSurface::resize
void OpenGlOffscreenSurface::resize(
int w,
int h)
{
resize(QSize(w, h));
}
bool OpenGlOffscreenSurface::event(QEvent* event)
{
switch (event->type()) {
case QEvent::UpdateLater:
update();
return (true);
case QEvent::UpdateRequest:
render();
return (true);
default:
return (false);
} // switch
} // OpenGlOffscreenSurface::event
QSize OpenGlOffscreenSurface::bufferSize() const
{
return (m_size);
}
使用OpenGlOffscreenSurface
:
Header ExamplePaintSurface.h
:
#ifndef EXAMPLEPAINTSURFACE_H
#define EXAMPLEPAINTSURFACE_H
#include "OpenGlOffscreenSurface.h"
class ExamplePaintSurface
: public OpenGlOffscreenSurface
{
public:
explicit ExamplePaintSurface(
QScreen* targetScreen = nullptr,
const QSize& size = QSize (1, 1));
virtual ~ExamplePaintSurface() override;
protected:
virtual void initializeGL() override;
virtual void resizeGL(
int width,
int height) override;
virtual void paintGL() override;
};
#endif // EXAMPLEPAINTSURFACE_H
来源ExamplePaintSurface.cpp
:
#include "ExamplePaintSurface.h"
#include <QPainter>
ExamplePaintSurface::ExamplePaintSurface(
QScreen* targetScreen,
const QSize& size)
: OpenGlOffscreenSurface(targetScreen, size) {}
ExamplePaintSurface::~ExamplePaintSurface() {}
void ExamplePaintSurface::initializeGL() {}
void ExamplePaintSurface::resizeGL(int width, int height) {}
void ExamplePaintSurface::paintGL()
{
QPainter painter(getPaintDevice());
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.drawText(20, 40, "Test"); // <-- drawing here
painter.end();
}
来源main.cpp
:
#include <QApplication>
#include "ExamplePaintSurface.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ExamplePaintSurface paintSurface;
paintSurface.resize(300, 200);
paintSurface.render();
QImage image = paintSurface.grabFramebuffer();
image.save(QString("image.png"));
return a.exec();
}
带有 QOffscreenSurface 的部分示例来自 此处:
#include <QGuiApplication>
#include <QSurfaceFormat>
#include <QOpenGlContext>
#include <QOffscreenSurface>
#include <QOpenGLFunctions_4_3_Core>
#include <QDebug>
int main(int argc, char* argv[])
{
QGuiApplication a(argc, argv);
QSurfaceFormat surfaceFormat;
surfaceFormat.setMajorVersion(4);
surfaceFormat.setMinorVersion(3);
QOpenGLContext openGLContext;
openGLContext.setFormat(surfaceFormat);
openGLContext.create();
if(!openGLContext.isValid()) return -1;
QOffscreenSurface surface;
surface.setFormat(surfaceFormat);
surface.create();
if(!surface.isValid()) return -2;
openGLContext.makeCurrent(&surface);
QOpenGLFunctions_4_3_Core f;
if(!f.initializeOpenGLFunctions()) return -3;
qDebug() << QString::fromLatin1((const char*)f.glGetString(GL_VERSION));
return 0;
}
而 here 是一个关于它的答案:
For offscreen rendering, try rendering into a QOpenGLFramebufferObject, which can be converted into a
QImage
, which in turn can easily be saved to disk.For that however, you still need a surface to render onto (as required by
QOpenGLContext::makeCurrent()
), so your only choice is indeed using 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 usingQOpenGLFramebufferObject
, and then callQOpenGLFramebufferObject::toImage()
to get access to the pixels.