Start supporting purely offscreen QOpenGLWidget

Due to popular demand. It does have it benefits (especially when
it comes to convenience) to allow grabbing QOpenGLWidgets even
when they are not part of an actual window and are not actually
visible.

Does not involve much more than dropping the warnings and bailouts
when there is active native window (because the QOpenGLWidget/its
parents are still hidden).

In addition the device pixel ratio from metric() has to be fixed
as well.

[ChangeLog][Qt Widgets] QOpenGLWidget is now able to render and
return its content via grabFramebuffer(), QWidget::grab() or
QWidget::render() even when the widget has not been made visible.

Task-number: QTBUG-47185
Task-number: QTBUG-61280
Change-Id: Icc2b0b3ce9778a3eb6409d54744238568abb0f0d
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
Laszlo Agocs 2017-06-07 14:35:41 +02:00
parent 41ae544c40
commit 2ea90c56f2
3 changed files with 95 additions and 12 deletions

View File

@ -791,10 +791,8 @@ void QOpenGLWidgetPrivate::initialize()
// texture usable by the underlying window's backingstore.
QWidget *tlw = q->window();
QOpenGLContext *shareContext = get(tlw)->shareContext();
if (Q_UNLIKELY(!shareContext)) {
qWarning("QOpenGLWidget: Cannot be used without a context shared with the toplevel.");
return;
}
// If shareContext is null, showing content on-screen will not work.
// However, offscreen rendering and grabFramebuffer() will stay fully functional.
// Do not include the sample count. Requesting a multisampled context is not necessary
// since we render into an FBO, never to an actual surface. What's more, attempting to
@ -804,9 +802,11 @@ void QOpenGLWidgetPrivate::initialize()
requestedFormat.setSamples(0);
QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext);
ctx->setShareContext(shareContext);
ctx->setFormat(requestedFormat);
ctx->setScreen(shareContext->screen());
if (shareContext) {
ctx->setShareContext(shareContext);
ctx->setScreen(shareContext->screen());
}
if (Q_UNLIKELY(!ctx->create())) {
qWarning("QOpenGLWidget: Failed to create context");
return;
@ -815,7 +815,9 @@ void QOpenGLWidgetPrivate::initialize()
// Propagate settings that make sense only for the tlw. Note that this only
// makes sense for properties that get picked up even after the native
// window is created.
QSurfaceFormat tlwFormat = tlw->windowHandle()->format();
QSurfaceFormat tlwFormat;
if (tlw->windowHandle())
tlwFormat = tlw->windowHandle()->format();
if (requestedFormat.swapInterval() != tlwFormat.swapInterval()) {
// Most platforms will pick up the changed swap interval on the next
// makeCurrent or swapBuffers.
@ -918,9 +920,14 @@ extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_
QImage QOpenGLWidgetPrivate::grabFramebuffer()
{
Q_Q(QOpenGLWidget);
initialize();
if (!initialized)
return QImage();
if (!fbo) // could be completely offscreen, without ever getting a resize event
recreateFbo();
if (!inPaintGL)
render();
@ -1371,7 +1378,7 @@ int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
if (window)
return int(window->devicePixelRatio() * devicePixelRatioFScale());
else
return 1.0;
return int(devicePixelRatioFScale());
default:
qWarning("QOpenGLWidget::metric(): unknown metric %d", metric);
return 0;
@ -1422,7 +1429,14 @@ bool QOpenGLWidget::event(QEvent *e)
d->reset();
// FALLTHROUGH
case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too
if (!d->initialized && !size().isEmpty() && window() && window()->windowHandle()) {
if (d->initialized && window()->windowHandle()
&& d->context->shareContext() != QWidgetPrivate::get(window())->shareContext())
{
// Special case: did grabFramebuffer() for a hidden widget that then became visible.
// Recreate all resources since the context now needs to share with the TLW's.
d->reset();
}
if (!d->initialized && !size().isEmpty() && window()->windowHandle()) {
d->initialize();
if (d->initialized)
d->recreateFbo();

View File

@ -12229,10 +12229,9 @@ QOpenGLContext *QWidgetPrivate::shareContext() const
#ifdef QT_NO_OPENGL
return 0;
#else
if (Q_UNLIKELY(!extra || !extra->topextra || !extra->topextra->window)) {
qWarning("Asking for share context for widget that does not have a window handle");
if (Q_UNLIKELY(!extra || !extra->topextra || !extra->topextra->window))
return 0;
}
QWidgetPrivate *that = const_cast<QWidgetPrivate *>(this);
if (!extra->topextra->shareContext) {
QOpenGLContext *ctx = new QOpenGLContext;

View File

@ -61,6 +61,8 @@ private slots:
void showHide();
void nativeWindow();
void stackWidgetOpaqueChildIsVisible();
void offscreen();
void offscreenThenOnscreen();
};
void tst_QOpenGLWidget::initTestCase()
@ -572,6 +574,74 @@ void tst_QOpenGLWidget::stackWidgetOpaqueChildIsVisible()
#undef VERIFY_COLOR
}
void tst_QOpenGLWidget::offscreen()
{
{
QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600));
w->resize(800, 600);
w->setClearColor(0, 0, 1);
QImage image = w->grabFramebuffer();
QVERIFY(!image.isNull());
QCOMPARE(image.width(), w->width());
QCOMPARE(image.height(), w->height());
QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255));
}
// QWidget::grab() should eventually end up in grabFramebuffer() as well
{
QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600));
w->resize(800, 600);
w->setClearColor(0, 0, 1);
QPixmap pm = w->grab();
QImage image = pm.toImage();
QVERIFY(!image.isNull());
QCOMPARE(image.width(), w->width());
QCOMPARE(image.height(), w->height());
QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255));
}
// ditto for QWidget::render()
{
QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600));
w->resize(800, 600);
w->setClearColor(0, 0, 1);
QImage image(800, 600, QImage::Format_ARGB32);
w->render(&image);
QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255));
}
}
void tst_QOpenGLWidget::offscreenThenOnscreen()
{
QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600));
w->resize(800, 600);
w->setClearColor(0, 0, 1);
QImage image = w->grabFramebuffer();
QVERIFY(!image.isNull());
QCOMPARE(image.width(), w->width());
QCOMPARE(image.height(), w->height());
QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255));
// now let's make things more challenging: show. Internally this needs
// recreating the context.
w->show();
QTest::qWaitForWindowExposed(w.data());
image = w->grabFramebuffer();
QVERIFY(!image.isNull());
QCOMPARE(image.width(), w->width());
QCOMPARE(image.height(), w->height());
QVERIFY(image.pixel(30, 40) == qRgb(0, 0, 255));
}
QTEST_MAIN(tst_QOpenGLWidget)
#include "tst_qopenglwidget.moc"