Resurrect QGLWidget::renderPixmap()

This function has been completely broken since Qt 5.0. Unfortunately
the autotest's verification steps were somewhat faulty so returning
a null pixmap from renderPixmap() did not trigger a failure.

The implementation is now done with framebuffer objects and pixel
readback. This is not ideal performance-wise, but is the only option.
In Qt 4 pixmaps were often backed by platform dependent native pixmaps
that could be used as rendering targets for OpenGL content. This is not
an option anymore since pixmaps are raster backed on all the major platforms.

Task-number: QTBUG-33186
Change-Id: I8f558e33bf7967ac3be439fd5a3eac07b6444be5
Reviewed-by: Jørgen Lind <jorgen.lind@digia.com>
bb10
Laszlo Agocs 2013-12-30 17:09:12 +01:00 committed by The Qt Project
parent 63fd793fc3
commit bd5cea5ba8
4 changed files with 65 additions and 48 deletions

View File

@ -2975,6 +2975,10 @@ bool QGLContext::areSharing(const QGLContext *context1, const QGLContext *contex
Returns \c true if the paint device of this context is a pixmap;
otherwise returns \c false.
Since Qt 5 the paint device is never actually a pixmap. renderPixmap() is
however still simulated using framebuffer objects and readbacks, and this
function will return \c true in this case.
*/
/*!
@ -3143,7 +3147,7 @@ QGLFormat QGLContext::requestedFormat() const
bool QGLContext::deviceIsPixmap() const
{
Q_D(const QGLContext);
return d->paintDevice->devType() == QInternal::Pixmap;
return !d->readback_target_size.isEmpty();
}
@ -3885,7 +3889,9 @@ void QGLWidget::setFormat(const QGLFormat &format)
void QGLWidget::updateGL()
{
if (updatesEnabled() && testAttribute(Qt::WA_Mapped))
Q_D(QGLWidget);
const bool targetIsOffscreen = !d->glcx->d_ptr->readback_target_size.isEmpty();
if (updatesEnabled() && (testAttribute(Qt::WA_Mapped) || targetIsOffscreen))
glDraw();
}
@ -4041,20 +4047,28 @@ void QGLWidget::paintEvent(QPaintEvent *)
You can use this method on both visible and invisible QGLWidget objects.
This method will create a pixmap and a temporary QGLContext to
render on the pixmap. It will then call initializeGL(),
resizeGL(), and paintGL() on this context. Finally, the widget's
original GL context is restored.
Internally the function renders into a framebuffer object and performs pixel
readback. This has a performance penalty, meaning that this function is not
suitable to be called at a high frequency.
The size of the pixmap will be \a w pixels wide and \a h pixels
high unless one of these parameters is 0 (the default), in which
case the pixmap will have the same size as the widget.
After creating and binding the framebuffer object, the function will call
initializeGL(), resizeGL(), and paintGL(). On the next normal update
initializeGL() and resizeGL() will be triggered again since the size of the
destination pixmap and the QGLWidget's size may differ.
If \a useContext is true, this method will try to be more
efficient by using the existing GL context to render the pixmap.
The default is false. Only use true if you understand the risks.
Note that under Windows a temporary context has to be created
and usage of the \e useContext parameter is not supported.
The size of the pixmap will be \a w pixels wide and \a h pixels high unless
one of these parameters is 0 (the default), in which case the pixmap will
have the same size as the widget.
Care must be taken when using framebuffer objects in paintGL() in
combination with this function. To switch back to the default framebuffer,
use QGLFramebufferObject::bindDefault(). Binding FBO 0 is wrong since
renderPixmap() uses a custom framebuffer instead of the one provided by the
windowing system.
\a useContext is ignored. Historically this parameter enabled the usage of
the existing GL context. This is not supported anymore since additional
contexts are never created.
Overlays are not rendered onto the pixmap.
@ -4069,43 +4083,31 @@ void QGLWidget::paintEvent(QPaintEvent *)
QPixmap QGLWidget::renderPixmap(int w, int h, bool useContext)
{
Q_UNUSED(useContext);
Q_D(QGLWidget);
QSize sz = size();
if ((w > 0) && (h > 0))
sz = QSize(w, h);
QPixmap pm(sz);
d->glcx->doneCurrent();
bool success = true;
if (useContext && isValid() && d->renderCxPm(&pm))
return pm;
QGLFormat fmt = d->glcx->requestedFormat();
fmt.setDirectRendering(false); // Direct is unlikely to work
fmt.setDoubleBuffer(false); // We don't need dbl buf
QGLContext* ocx = d->glcx;
ocx->doneCurrent();
d->glcx = new QGLContext(fmt, &pm);
d->glcx->create();
if (d->glcx->isValid())
QPixmap pm;
if (d->glcx->isValid()) {
d->glcx->makeCurrent();
QGLFramebufferObject fbo(sz, QGLFramebufferObject::CombinedDepthStencil);
fbo.bind();
d->glcx->setInitialized(false);
uint prevDefaultFbo = d->glcx->d_ptr->default_fbo;
d->glcx->d_ptr->default_fbo = fbo.handle();
d->glcx->d_ptr->readback_target_size = sz;
updateGL();
else
success = false;
delete d->glcx;
d->glcx = ocx;
ocx->makeCurrent();
if (success) {
return pm;
fbo.release();
pm = QPixmap::fromImage(fbo.toImage());
d->glcx->d_ptr->default_fbo = prevDefaultFbo;
d->glcx->setInitialized(false);
d->glcx->d_ptr->readback_target_size = QSize();
}
return QPixmap();
return pm;
}
/*!
@ -4164,14 +4166,23 @@ void QGLWidget::glDraw()
if (d->glcx->deviceIsPixmap())
glDrawBuffer(GL_FRONT);
#endif
QSize readback_target_size = d->glcx->d_ptr->readback_target_size;
if (!d->glcx->initialized()) {
glInit();
const qreal scaleFactor = (window() && window()->windowHandle()) ?
window()->windowHandle()->devicePixelRatio() : 1.0;
resizeGL(d->glcx->device()->width() * scaleFactor, d->glcx->device()->height() * scaleFactor); // New context needs this "resize"
int w, h;
if (readback_target_size.isEmpty()) {
w = d->glcx->device()->width() * scaleFactor;
h = d->glcx->device()->height() * scaleFactor;
} else {
w = readback_target_size.width();
h = readback_target_size.height();
}
resizeGL(w, h); // New context needs this "resize"
}
paintGL();
if (doubleBuffer()) {
if (doubleBuffer() && readback_target_size.isEmpty()) {
if (d->autoSwap)
swapBuffers();
} else {

View File

@ -270,6 +270,7 @@ public:
uint workaround_brokenAlphaTexSubImage_init : 1;
QPaintDevice *paintDevice;
QSize readback_target_size;
QColor transpColor;
QGLContext *q_ptr;
QGLFormat::OpenGLVersionFlags version_flags;

View File

@ -138,6 +138,9 @@ bool QGLContext::chooseContext(const QGLContext* shareContext)
{
Q_D(QGLContext);
if(!d->paintDevice || d->paintDevice->devType() != QInternal::Widget) {
// Unlike in Qt 4, the only possible target is a widget backed by an OpenGL-based
// QWindow. Pixmaps in particular are not supported anymore as paint devices since
// starting from Qt 5 QPixmap is raster-backed on almost all platforms.
d->valid = false;
}else {
QWidget *widget = static_cast<QWidget *>(d->paintDevice);

View File

@ -1380,12 +1380,13 @@ void tst_QGL::glWidgetRenderPixmap()
{
RenderPixmapWidget *w = new RenderPixmapWidget;
QPixmap pm = w->renderPixmap(100, 100, false);
QSize pmSize = QSize(100, 100);
QPixmap pm = w->renderPixmap(pmSize.width(), pmSize.height(), false);
delete w;
QImage fb = pm.toImage().convertToFormat(QImage::Format_RGB32);
QImage reference(fb.size(), QImage::Format_RGB32);
QImage reference(pmSize, QImage::Format_RGB32);
reference.fill(0xffff0000);
QFUZZY_COMPARE_IMAGES(fb, reference);
@ -2011,6 +2012,7 @@ void tst_QGL::textureCleanup()
QGLWidget w;
w.resize(200,200);
w.show();
QTest::qWaitForWindowExposed(&w);
w.makeCurrent();
// Test pixmaps which have been loaded via QPixmapCache are removed from the texture cache