summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKristoffer Skau <kristoffer.skau@qt.io>2022-11-24 14:31:09 +0100
committerKristoffer Skau <kristoffer.skau@qt.io>2022-12-01 13:42:07 +0100
commitc450f6d21c5153e05bd10afdd54767623cfbe7e8 (patch)
treee7ce6d73cda2f0f83673cad8b528bb67463e9cdd
parent9bc74d14f379ad58d7a80d5514a3db5be5012de0 (diff)
Support stereoscopic rendering with QGraphicsView
This patch adds a manual test and the required work in graphicsview and qwidget private apis to support stereoscopic rendeing. Basically it works by doing the drawing in QGraphicsView::paintEvent twice, once for each buffer. This way the scene items are rendered to both buffers. There's also an update to resolvement in QOpenGLWidgetPrivate so that multisampling works correctly. [ChangeLog][Widgets][QGraphicsView] Added support for stereoscopic rendering. Task-number: QTBUG-64587 Change-Id: I20650682daa805b64fe7f0d2ba086917d3f12229 Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
-rw-r--r--src/openglwidgets/qopenglwidget.cpp96
-rw-r--r--src/openglwidgets/qopenglwidget.h1
-rw-r--r--src/widgets/graphicsview/qgraphicsview.cpp253
-rw-r--r--src/widgets/graphicsview/qgraphicsview_p.h2
-rw-r--r--src/widgets/kernel/qwidget_p.h5
-rw-r--r--tests/manual/stereographicsview/CMakeLists.txt32
-rw-r--r--tests/manual/stereographicsview/main.cpp26
-rw-r--r--tests/manual/stereographicsview/mainwindow.cpp79
-rw-r--r--tests/manual/stereographicsview/mainwindow.h30
-rw-r--r--tests/manual/stereographicsview/mainwindow.ui101
-rw-r--r--tests/manual/stereographicsview/mygraphicsview.cpp209
-rw-r--r--tests/manual/stereographicsview/mygraphicsview.h47
-rw-r--r--tests/manual/stereographicsview/stereographicsview.pro13
13 files changed, 763 insertions, 131 deletions
diff --git a/src/openglwidgets/qopenglwidget.cpp b/src/openglwidgets/qopenglwidget.cpp
index 7a0ac05cee..4d8c181c22 100644
--- a/src/openglwidgets/qopenglwidget.cpp
+++ b/src/openglwidgets/qopenglwidget.cpp
@@ -552,7 +552,7 @@ public:
void destroyFbos();
- void setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
+ bool setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
QImage grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer);
QImage grabFramebuffer() override;
void beginBackingStorePainting() override { inBackingStorePaint = true; }
@@ -560,9 +560,13 @@ public:
void beginCompose() override;
void endCompose() override;
void initializeViewportFramebuffer() override;
+ bool isStereoEnabled() override;
+ bool toggleStereoTargetBuffer() override;
void resizeViewportFramebuffer() override;
void resolveSamples() override;
+ void resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
+
QOpenGLContext *context = nullptr;
QRhiTexture *wrapperTextures[2] = {};
QOpenGLFramebufferObject *fbos[2] = {};
@@ -697,8 +701,6 @@ void QOpenGLWidgetPrivate::reset()
void QOpenGLWidgetPrivate::resetRhiDependentResources()
{
- Q_Q(QOpenGLWidget);
-
// QRhi resource created from the QRhi. These must be released whenever the
// widget gets associated with a different QRhi, even when all OpenGL
// contexts share resources.
@@ -706,7 +708,7 @@ void QOpenGLWidgetPrivate::resetRhiDependentResources()
delete wrapperTextures[0];
wrapperTextures[0] = nullptr;
- if (q->format().stereo()) {
+ if (isStereoEnabled()) {
delete wrapperTextures[1];
wrapperTextures[1] = nullptr;
}
@@ -738,9 +740,9 @@ void QOpenGLWidgetPrivate::recreateFbos()
if (samples > 0)
resolvedFbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize);
- const bool stereoEnabled = q->format().stereo();
+ const bool stereo = isStereoEnabled();
- if (stereoEnabled) {
+ if (stereo) {
fbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
if (samples > 0)
resolvedFbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize);
@@ -753,11 +755,12 @@ void QOpenGLWidgetPrivate::recreateFbos()
context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
ensureRhiDependentResources();
- if (stereoEnabled) {
+ if (stereo) {
currentTargetBuffer = QOpenGLWidget::RightBuffer;
fbos[currentTargetBuffer]->bind();
context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
ensureRhiDependentResources();
+ currentTargetBuffer = QOpenGLWidget::LeftBuffer;
}
flushPending = true; // Make sure the FBO is initialized before use
@@ -895,11 +898,17 @@ void QOpenGLWidgetPrivate::initialize()
void QOpenGLWidgetPrivate::resolveSamples()
{
+ resolveSamplesForBuffer(QOpenGLWidget::LeftBuffer);
+ resolveSamplesForBuffer(QOpenGLWidget::RightBuffer);
+}
+
+void QOpenGLWidgetPrivate::resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
+{
Q_Q(QOpenGLWidget);
- if (resolvedFbos[currentTargetBuffer]) {
- q->makeCurrent();
- QRect rect(QPoint(0, 0), fbos[currentTargetBuffer]->size());
- QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[currentTargetBuffer], rect, fbos[currentTargetBuffer], rect);
+ if (resolvedFbos[targetBuffer]) {
+ q->makeCurrent(targetBuffer);
+ QRect rect(QPoint(0, 0), fbos[targetBuffer]->size());
+ QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[targetBuffer], rect, fbos[targetBuffer], rect);
flushPending = true;
}
}
@@ -924,8 +933,8 @@ void QOpenGLWidgetPrivate::render()
return;
}
- const bool stereoEnabled = q->format().stereo();
- if (stereoEnabled) {
+ const bool stereo = isStereoEnabled();
+ if (stereo) {
static bool warningGiven = false;
if (!fbos[QOpenGLWidget::RightBuffer] && !warningGiven) {
qWarning("QOpenGLWidget: Stereo is enabled, but no right buffer. Using only left buffer");
@@ -936,7 +945,7 @@ void QOpenGLWidgetPrivate::render()
if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
invalidateFbo();
- if (stereoEnabled && fbos[QOpenGLWidget::RightBuffer]) {
+ if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
invalidateFbo();
setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
@@ -952,7 +961,7 @@ void QOpenGLWidgetPrivate::render()
QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
q->paintGL();
- if (stereoEnabled && fbos[QOpenGLWidget::RightBuffer]) {
+ if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
q->paintGL();
@@ -1017,7 +1026,7 @@ QImage QOpenGLWidgetPrivate::grabFramebuffer(QOpenGLWidget::TargetBuffer targetB
// The second fbo is only created when stereoscopic rendering is enabled
// Just use the default one if not.
- if (targetBuffer == QOpenGLWidget::RightBuffer && !q->format().stereo())
+ if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
targetBuffer = QOpenGLWidget::LeftBuffer;
if (!fbos[targetBuffer]) // could be completely offscreen, without ever getting a resize event
@@ -1028,7 +1037,7 @@ QImage QOpenGLWidgetPrivate::grabFramebuffer(QOpenGLWidget::TargetBuffer targetB
setCurrentTargetBuffer(targetBuffer);
if (resolvedFbos[targetBuffer]) {
- resolveSamples();
+ resolveSamplesForBuffer(targetBuffer);
resolvedFbos[targetBuffer]->bind();
}
@@ -1054,6 +1063,22 @@ void QOpenGLWidgetPrivate::initializeViewportFramebuffer()
q->setAutoFillBackground(true);
}
+bool QOpenGLWidgetPrivate::isStereoEnabled()
+{
+ Q_Q(QOpenGLWidget);
+ // Note that because this internally might use the requested format,
+ // then this can return a false positive on hardware where
+ // steroscopic rendering is not supported.
+ return q->format().stereo();
+}
+
+bool QOpenGLWidgetPrivate::toggleStereoTargetBuffer()
+{
+ return setCurrentTargetBuffer(currentTargetBuffer == QOpenGLWidget::LeftBuffer ?
+ QOpenGLWidget::RightBuffer :
+ QOpenGLWidget::LeftBuffer);
+}
+
void QOpenGLWidgetPrivate::resizeViewportFramebuffer()
{
Q_Q(QOpenGLWidget);
@@ -1254,6 +1279,34 @@ void QOpenGLWidget::makeCurrent()
}
/*!
+ Prepares for rendering OpenGL content for this widget by making the
+ context for the passed in buffer current and binding the framebuffer object in that
+ context.
+
+ \note This only makes sense to call when stereoscopic rendering is enabled.
+ Nothing will happen if the right buffer is requested when it's disabled.
+
+ It is not necessary to call this function in most cases, because it
+ is called automatically before invoking paintGL().
+
+ \since 6.5
+
+ \sa context(), paintGL(), doneCurrent()
+ */
+void QOpenGLWidget::makeCurrent(TargetBuffer targetBuffer)
+{
+ Q_D(QOpenGLWidget);
+ if (!d->initialized)
+ return;
+
+ // The FBO for the right buffer is only initialized when stereo is set
+ if (targetBuffer == TargetBuffer::RightBuffer && !format().stereo())
+ return;
+
+ d->setCurrentTargetBuffer(targetBuffer); // calls makeCurrent
+}
+
+/*!
Releases the context.
It is not necessary to call this function in most cases, since the
@@ -1585,11 +1638,18 @@ QPaintEngine *QOpenGLWidget::paintEngine() const
return d->paintDevice->paintEngine();
}
-void QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
+
+bool QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
{
Q_Q(QOpenGLWidget);
+
+ if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
+ return false;
+
currentTargetBuffer = targetBuffer;
q->makeCurrent();
+
+ return true;
}
/*!
diff --git a/src/openglwidgets/qopenglwidget.h b/src/openglwidgets/qopenglwidget.h
index 84097854e5..847e684cd9 100644
--- a/src/openglwidgets/qopenglwidget.h
+++ b/src/openglwidgets/qopenglwidget.h
@@ -45,6 +45,7 @@ public:
bool isValid() const;
void makeCurrent();
+ void makeCurrent(TargetBuffer targetBuffer);
void doneCurrent();
QOpenGLContext *context() const;
diff --git a/src/widgets/graphicsview/qgraphicsview.cpp b/src/widgets/graphicsview/qgraphicsview.cpp
index 8e362ce1e0..f9502b938a 100644
--- a/src/widgets/graphicsview/qgraphicsview.cpp
+++ b/src/widgets/graphicsview/qgraphicsview.cpp
@@ -85,6 +85,16 @@ static const int QGRAPHICSVIEW_PREALLOC_STYLE_OPTIONS = 503; // largest prime <
view coordinates and scene coordinates, and to find items on the scene
using view coordinates.
+ When using a QOpenGLWidget as a viewport, stereoscopic rendering is
+ supported. This is done using the same pattern as QOpenGLWidget::paintGL.
+ To enable it, enable the QSurfaceFormat::StereoBuffers flag. Because of
+ how the flag is handled internally, set QSurfaceFormat::StereoBuffers flag
+ globally before the window is created using QSurfaceFormat::setDefaultFormat().
+ If the flag is enabled and there is hardware support for stereoscopic
+ rendering, then drawBackground() and drawForeground() will be triggered twice
+ each frame. Call QOpenGLWidget::currentTargetBuffer() to query which buffer
+ is currently being drawn to.
+
\image graphicsview-view.png
\note Using an OpenGL viewport limits the ability to use QGraphicsProxyWidget.
@@ -2724,6 +2734,9 @@ void QGraphicsView::setupViewport(QWidget *widget)
widget->setFocusPolicy(Qt::StrongFocus);
+ if (isGLWidget)
+ d->stereoEnabled = QWidgetPrivate::get(widget)->isStereoEnabled();
+
if (!isGLWidget) {
// autoFillBackground enables scroll acceleration.
widget->setAutoFillBackground(true);
@@ -3425,132 +3438,146 @@ void QGraphicsView::paintEvent(QPaintEvent *event)
painter.setWorldTransform(viewportTransform());
const QTransform viewTransform = painter.worldTransform();
- // Draw background
- if (d->cacheMode & CacheBackground) {
- // Recreate the background pixmap, and flag the whole background as
- // exposed.
- if (d->mustResizeBackgroundPixmap) {
- const qreal dpr = d->viewport->devicePixelRatio();
- d->backgroundPixmap = QPixmap(viewport()->size() * dpr);
- d->backgroundPixmap.setDevicePixelRatio(dpr);
- QBrush bgBrush = viewport()->palette().brush(viewport()->backgroundRole());
- if (!bgBrush.isOpaque())
- d->backgroundPixmap.fill(Qt::transparent);
- QPainter p(&d->backgroundPixmap);
- p.fillRect(0, 0, d->backgroundPixmap.width(), d->backgroundPixmap.height(), bgBrush);
- d->backgroundPixmapExposed = QRegion(viewport()->rect());
- d->mustResizeBackgroundPixmap = false;
- }
+ const auto actuallyDraw = [&]() {
+ // Draw background
+ if (d->cacheMode & CacheBackground) {
+ // Recreate the background pixmap, and flag the whole background as
+ // exposed.
+ if (d->mustResizeBackgroundPixmap) {
+ const qreal dpr = d->viewport->devicePixelRatio();
+ d->backgroundPixmap = QPixmap(viewport()->size() * dpr);
+ d->backgroundPixmap.setDevicePixelRatio(dpr);
+ QBrush bgBrush = viewport()->palette().brush(viewport()->backgroundRole());
+ if (!bgBrush.isOpaque())
+ d->backgroundPixmap.fill(Qt::transparent);
+ QPainter p(&d->backgroundPixmap);
+ p.fillRect(0, 0, d->backgroundPixmap.width(), d->backgroundPixmap.height(), bgBrush);
+ d->backgroundPixmapExposed = QRegion(viewport()->rect());
+ d->mustResizeBackgroundPixmap = false;
+ }
- // Redraw exposed areas
- if (!d->backgroundPixmapExposed.isEmpty()) {
- QPainter backgroundPainter(&d->backgroundPixmap);
- backgroundPainter.setClipRegion(d->backgroundPixmapExposed, Qt::ReplaceClip);
- if (viewTransformed)
- backgroundPainter.setTransform(viewTransform);
- QRectF backgroundExposedSceneRect = mapToScene(d->backgroundPixmapExposed.boundingRect()).boundingRect();
- drawBackground(&backgroundPainter, backgroundExposedSceneRect);
- d->backgroundPixmapExposed = QRegion();
- }
+ // Redraw exposed areas
+ if (!d->backgroundPixmapExposed.isEmpty()) {
+ QPainter backgroundPainter(&d->backgroundPixmap);
+ backgroundPainter.setClipRegion(d->backgroundPixmapExposed, Qt::ReplaceClip);
+ if (viewTransformed)
+ backgroundPainter.setTransform(viewTransform);
+ QRectF backgroundExposedSceneRect = mapToScene(d->backgroundPixmapExposed.boundingRect()).boundingRect();
+ drawBackground(&backgroundPainter, backgroundExposedSceneRect);
+ d->backgroundPixmapExposed = QRegion();
+ }
- // Blit the background from the background pixmap
- if (viewTransformed) {
- painter.setWorldTransform(QTransform());
- painter.drawPixmap(QPoint(), d->backgroundPixmap);
- painter.setWorldTransform(viewTransform);
+ // Blit the background from the background pixmap
+ if (viewTransformed) {
+ painter.setWorldTransform(QTransform());
+ painter.drawPixmap(QPoint(), d->backgroundPixmap);
+ painter.setWorldTransform(viewTransform);
+ } else {
+ painter.drawPixmap(QPoint(), d->backgroundPixmap);
+ }
} else {
- painter.drawPixmap(QPoint(), d->backgroundPixmap);
+ if (!(d->optimizationFlags & DontSavePainterState))
+ painter.save();
+
+ drawBackground(&painter, exposedSceneRect);
+ if (!(d->optimizationFlags & DontSavePainterState))
+ painter.restore();
}
- } else {
- if (!(d->optimizationFlags & DontSavePainterState))
- painter.save();
- drawBackground(&painter, exposedSceneRect);
- if (!(d->optimizationFlags & DontSavePainterState))
- painter.restore();
- }
- // Items
- if (!(d->optimizationFlags & IndirectPainting)) {
- const quint32 oldRectAdjust = d->scene->d_func()->rectAdjust;
- if (d->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing)
- d->scene->d_func()->rectAdjust = 1;
- else
- d->scene->d_func()->rectAdjust = 2;
- d->scene->d_func()->drawItems(&painter, viewTransformed ? &viewTransform : nullptr,
- &d->exposedRegion, viewport());
- d->scene->d_func()->rectAdjust = oldRectAdjust;
- // Make sure the painter's world transform is restored correctly when
- // drawing without painter state protection (DontSavePainterState).
- // We only change the worldTransform() so there's no need to do a full-blown
- // save() and restore(). Also note that we don't have to do this in case of
- // IndirectPainting (the else branch), because in that case we always save()
- // and restore() in QGraphicsScene::drawItems().
- if (!d->scene->d_func()->painterStateProtection)
- painter.setOpacity(1.0);
- painter.setWorldTransform(viewTransform);
- } else {
- // Make sure we don't have unpolished items before we draw
- if (!d->scene->d_func()->unpolishedItems.isEmpty())
- d->scene->d_func()->_q_polishItems();
- // We reset updateAll here (after we've issued polish events)
- // so that we can discard update requests coming from polishEvent().
- d->scene->d_func()->updateAll = false;
-
- // Find all exposed items
- bool allItems = false;
- QList<QGraphicsItem *> itemList = d->findItems(d->exposedRegion, &allItems, viewTransform);
- if (!itemList.isEmpty()) {
- // Generate the style options.
- const int numItems = itemList.size();
- QGraphicsItem **itemArray = &itemList[0]; // Relies on QList internals, but is perfectly valid.
- QStyleOptionGraphicsItem *styleOptionArray = d->allocStyleOptionsArray(numItems);
- QTransform transform(Qt::Uninitialized);
- for (int i = 0; i < numItems; ++i) {
- QGraphicsItem *item = itemArray[i];
- QGraphicsItemPrivate *itemd = item->d_ptr.data();
- itemd->initStyleOption(&styleOptionArray[i], viewTransform, d->exposedRegion, allItems);
- // Cache the item's area in view coordinates.
- // Note that we have to do this here in case the base class implementation
- // (QGraphicsScene::drawItems) is not called. If it is, we'll do this
- // operation twice, but that's the price one has to pay for using indirect
- // painting :-/.
- const QRectF brect = adjustedItemEffectiveBoundingRect(item);
- if (!itemd->itemIsUntransformable()) {
- transform = item->sceneTransform();
- if (viewTransformed)
- transform *= viewTransform;
- } else {
- transform = item->deviceTransform(viewTransform);
+ // Items
+ if (!(d->optimizationFlags & IndirectPainting)) {
+ const quint32 oldRectAdjust = d->scene->d_func()->rectAdjust;
+ if (d->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing)
+ d->scene->d_func()->rectAdjust = 1;
+ else
+ d->scene->d_func()->rectAdjust = 2;
+ d->scene->d_func()->drawItems(&painter, viewTransformed ? &viewTransform : nullptr,
+ &d->exposedRegion, viewport());
+ d->scene->d_func()->rectAdjust = oldRectAdjust;
+ // Make sure the painter's world transform is restored correctly when
+ // drawing without painter state protection (DontSavePainterState).
+ // We only change the worldTransform() so there's no need to do a full-blown
+ // save() and restore(). Also note that we don't have to do this in case of
+ // IndirectPainting (the else branch), because in that case we always save()
+ // and restore() in QGraphicsScene::drawItems().
+ if (!d->scene->d_func()->painterStateProtection)
+ painter.setOpacity(1.0);
+ painter.setWorldTransform(viewTransform);
+ } else {
+ // Make sure we don't have unpolished items before we draw
+ if (!d->scene->d_func()->unpolishedItems.isEmpty())
+ d->scene->d_func()->_q_polishItems();
+ // We reset updateAll here (after we've issued polish events)
+ // so that we can discard update requests coming from polishEvent().
+ d->scene->d_func()->updateAll = false;
+
+ // Find all exposed items
+ bool allItems = false;
+ QList<QGraphicsItem *> itemList = d->findItems(d->exposedRegion, &allItems, viewTransform);
+ if (!itemList.isEmpty()) {
+ // Generate the style options.
+ const int numItems = itemList.size();
+ QGraphicsItem **itemArray = &itemList[0]; // Relies on QList internals, but is perfectly valid.
+ QStyleOptionGraphicsItem *styleOptionArray = d->allocStyleOptionsArray(numItems);
+ QTransform transform(Qt::Uninitialized);
+ for (int i = 0; i < numItems; ++i) {
+ QGraphicsItem *item = itemArray[i];
+ QGraphicsItemPrivate *itemd = item->d_ptr.data();
+ itemd->initStyleOption(&styleOptionArray[i], viewTransform, d->exposedRegion, allItems);
+ // Cache the item's area in view coordinates.
+ // Note that we have to do this here in case the base class implementation
+ // (QGraphicsScene::drawItems) is not called. If it is, we'll do this
+ // operation twice, but that's the price one has to pay for using indirect
+ // painting :-/.
+ const QRectF brect = adjustedItemEffectiveBoundingRect(item);
+ if (!itemd->itemIsUntransformable()) {
+ transform = item->sceneTransform();
+ if (viewTransformed)
+ transform *= viewTransform;
+ } else {
+ transform = item->deviceTransform(viewTransform);
+ }
+ itemd->paintedViewBoundingRects.insert(d->viewport, transform.mapRect(brect).toRect());
}
- itemd->paintedViewBoundingRects.insert(d->viewport, transform.mapRect(brect).toRect());
+ // Draw the items.
+ drawItems(&painter, numItems, itemArray, styleOptionArray);
+ d->freeStyleOptionsArray(styleOptionArray);
}
- // Draw the items.
- drawItems(&painter, numItems, itemArray, styleOptionArray);
- d->freeStyleOptionsArray(styleOptionArray);
}
- }
- // Foreground
- drawForeground(&painter, exposedSceneRect);
+ // Foreground
+ drawForeground(&painter, exposedSceneRect);
-#if QT_CONFIG(rubberband)
- // Rubberband
- if (d->rubberBanding && !d->rubberBandRect.isEmpty()) {
- painter.restore();
- QStyleOptionRubberBand option;
- option.initFrom(viewport());
- option.rect = d->rubberBandRect;
- option.shape = QRubberBand::Rectangle;
-
- QStyleHintReturnMask mask;
- if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option, viewport(), &mask)) {
- // painter clipping for masked rubberbands
- painter.setClipRegion(mask.region, Qt::IntersectClip);
+ #if QT_CONFIG(rubberband)
+ // Rubberband
+ if (d->rubberBanding && !d->rubberBandRect.isEmpty()) {
+ painter.restore();
+ QStyleOptionRubberBand option;
+ option.initFrom(viewport());
+ option.rect = d->rubberBandRect;
+ option.shape = QRubberBand::Rectangle;
+
+ QStyleHintReturnMask mask;
+ if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option, viewport(), &mask)) {
+ // painter clipping for masked rubberbands
+ painter.setClipRegion(mask.region, Qt::IntersectClip);
+ }
+
+ viewport()->style()->drawControl(QStyle::CE_RubberBand, &option, &painter, viewport());
}
+ #endif
+ };
- viewport()->style()->drawControl(QStyle::CE_RubberBand, &option, &painter, viewport());
+ actuallyDraw();
+
+ // For stereo we want to draw everything twice, once to each buffer
+ if (d->stereoEnabled) {
+ QWidgetPrivate* w = QWidgetPrivate::get(viewport());
+ if (w->toggleStereoTargetBuffer()) {
+ actuallyDraw();
+ w->toggleStereoTargetBuffer();
+ }
}
-#endif
painter.end();
diff --git a/src/widgets/graphicsview/qgraphicsview_p.h b/src/widgets/graphicsview/qgraphicsview_p.h
index f3388764b5..a15defe621 100644
--- a/src/widgets/graphicsview/qgraphicsview_p.h
+++ b/src/widgets/graphicsview/qgraphicsview_p.h
@@ -100,6 +100,8 @@ public:
QGraphicsView::ViewportUpdateMode viewportUpdateMode;
QGraphicsView::OptimizationFlags optimizationFlags;
+ bool stereoEnabled = false; // Set in setupViewport()
+
QPointer<QGraphicsScene> scene;
#if QT_CONFIG(rubberband)
QRect rubberBandRect;
diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
index ea03e9c708..25a9c75701 100644
--- a/src/widgets/kernel/qwidget_p.h
+++ b/src/widgets/kernel/qwidget_p.h
@@ -621,6 +621,11 @@ public:
// Called after each paint event.
virtual void resolveSamples() { }
+ // These two are used in QGraphicsView for supporting stereoscopic rendering with a
+ // QOpenGLWidget viewport.
+ virtual bool isStereoEnabled() { return false; } // Called in QGraphicsView::setupViewport
+ virtual bool toggleStereoTargetBuffer() { return false; } // Called in QGraphicsView::paintEvent
+
static void setWidgetParentHelper(QObject *widgetAsObject, QObject *newParent);
std::string flagsForDumping() const override;
diff --git a/tests/manual/stereographicsview/CMakeLists.txt b/tests/manual/stereographicsview/CMakeLists.txt
new file mode 100644
index 0000000000..937e10de91
--- /dev/null
+++ b/tests/manual/stereographicsview/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from stereographicsview.pro.
+
+#####################################################################
+## stereographicsview Binary:
+#####################################################################
+
+set(CMAKE_AUTOUIC ON)
+
+qt_internal_add_manual_test(stereographicsview
+ GUI
+ SOURCES
+ main.cpp
+ mainwindow.cpp
+ mainwindow.h
+ mainwindow.ui
+ mygraphicsview.h
+ mygraphicsview.cpp
+ LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::OpenGL
+ Qt::OpenGLWidgets
+ Qt::Widgets
+ Qt::WidgetsPrivate
+)
+
+#### Keys ignored in scope 1:.:.:stereographicsview.pro:<TRUE>:
+# TEMPLATE = "app"
diff --git a/tests/manual/stereographicsview/main.cpp b/tests/manual/stereographicsview/main.cpp
new file mode 100644
index 0000000000..6564e8efc4
--- /dev/null
+++ b/tests/manual/stereographicsview/main.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mainwindow.h"
+
+#include <QApplication>
+#include <QSurfaceFormat>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+
+ QSurfaceFormat format;
+ format.setDepthBufferSize(24);
+ format.setStencilBufferSize(8);
+ format.setSamples(16);
+ format.setStereo(true);
+ QSurfaceFormat::setDefaultFormat(format);
+
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
+
+
+#include "main.moc"
diff --git a/tests/manual/stereographicsview/mainwindow.cpp b/tests/manual/stereographicsview/mainwindow.cpp
new file mode 100644
index 0000000000..003b4da8c3
--- /dev/null
+++ b/tests/manual/stereographicsview/mainwindow.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mainwindow.h"
+#include "./ui_mainwindow.h"
+#include <QPushButton>
+#include <QSlider>
+#include <QLabel>
+#include <QGraphicsEllipseItem>
+#include <QRandomGenerator>
+#include <QOpenGLWidget>
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ ui->graphicsView->setViewport(new QOpenGLWidget);
+ ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
+
+ QRect rect = ui->graphicsView->rect();
+ QGraphicsScene *m_scene = new QGraphicsScene(rect);
+ ui->graphicsView->setScene(m_scene);
+
+ QSize initialSize(150, 150);
+ m_ellipse = m_scene->addEllipse(
+ rect.width() / 2 - initialSize.width() / 2,
+ rect.height() / 2 - initialSize.height() / 2,
+ initialSize.width(),
+ initialSize.height(),
+ QPen(Qt::magenta, 5),
+ Qt::blue);
+
+ QPushButton *button = new QPushButton();
+ button->setGeometry(QRect(rect.width() / 2 - 75, 100, 150, 50));
+ button->setText("Random ellipse color");
+ QObject::connect(button, &QPushButton::pressed, [&]() {
+ m_ellipse->setBrush(QColor::fromRgb(QRandomGenerator::global()->generate()));
+ m_ellipse->setPen(QPen(QColor::fromRgb(QRandomGenerator::global()->generate()), 5));
+ });
+ m_scene->addWidget(button);
+
+ m_label = new QLabel();
+ m_label->setGeometry(QRect(rect.width() / 2 - 50, rect.height() - 100, 100, 50));
+ m_label->setAlignment(Qt::AlignCenter);
+ m_scene->addWidget(m_label);
+
+ QSlider *slider = new QSlider(Qt::Horizontal);
+ slider->setGeometry(QRect(rect.width() / 2 - 100, rect.height() - 50, 200, 50));
+ slider->setMinimum(10);
+ slider->setMaximum(300);
+ slider->setValue(initialSize.width());
+ QObject::connect(slider, &QAbstractSlider::valueChanged, [this](int value){
+ QRectF rect = m_ellipse->rect();
+ rect.setWidth(value);
+ rect.setHeight(value);
+ m_ellipse->setRect(rect);
+ m_label->setText("Ellipse size: " + QString::number(value));
+ });
+ emit slider->valueChanged(initialSize.width());
+ m_scene->addWidget(slider);
+
+ QMenu *screenShotMenu = menuBar()->addMenu("&Screenshot");
+ screenShotMenu->addAction("Left buffer", this, [this](){
+ ui->graphicsView->saveImage(QOpenGLWidget::LeftBuffer);
+ });
+
+ screenShotMenu->addAction("Right buffer", this, [this](){
+ ui->graphicsView->saveImage(QOpenGLWidget::RightBuffer);
+ });
+
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
diff --git a/tests/manual/stereographicsview/mainwindow.h b/tests/manual/stereographicsview/mainwindow.h
new file mode 100644
index 0000000000..4a65dad68d
--- /dev/null
+++ b/tests/manual/stereographicsview/mainwindow.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class MainWindow; }
+QT_END_NAMESPACE
+
+class QGraphicsEllipseItem;
+class QLabel;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+private:
+ QGraphicsEllipseItem *m_ellipse;
+ QLabel *m_label;
+
+ Ui::MainWindow *ui;
+};
+#endif // MAINWINDOW_H
diff --git a/tests/manual/stereographicsview/mainwindow.ui b/tests/manual/stereographicsview/mainwindow.ui
new file mode 100644
index 0000000000..2ebb6b8caa
--- /dev/null
+++ b/tests/manual/stereographicsview/mainwindow.ui
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1120</width>
+ <height>695</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="MyGraphicsView" name="graphicsView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>QGraphicsView Stereoscopic Example</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>1120</width>
+ <height>21</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>MyGraphicsView</class>
+ <extends>QGraphicsView</extends>
+ <header>mygraphicsview.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/manual/stereographicsview/mygraphicsview.cpp b/tests/manual/stereographicsview/mygraphicsview.cpp
new file mode 100644
index 0000000000..dc6e39f4a0
--- /dev/null
+++ b/tests/manual/stereographicsview/mygraphicsview.cpp
@@ -0,0 +1,209 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mygraphicsview.h"
+#include <QResizeEvent>
+#include <QFileDialog>
+
+Q_OPENGL_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
+
+MyGraphicsView::MyGraphicsView(QWidget *parent) :
+ QGraphicsView(parent),
+ m_indexBuf(QOpenGLBuffer::IndexBuffer)
+{
+
+}
+
+MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent) :
+ QGraphicsView(scene, parent)
+{
+
+}
+
+void MyGraphicsView::saveImage(QOpenGLWidget::TargetBuffer targetBuffer)
+{
+ QOpenGLWidget *w = static_cast<QOpenGLWidget*>(viewport());
+ Q_ASSERT(w);
+
+ w->makeCurrent(targetBuffer);
+ draw(targetBuffer);
+
+ QImage img = qt_gl_read_framebuffer(w->size() * w->devicePixelRatio(), true, true);
+
+ if (img.isNull()) {
+ qFatal("Failed to grab framebuffer");
+ }
+
+ const char *fn =
+ targetBuffer == QOpenGLWidget::LeftBuffer
+ ? "leftBuffer.png" : "rightBuffer.png";
+
+ QFileDialog fd(this);
+ fd.setAcceptMode(QFileDialog::AcceptSave);
+ fd.setDefaultSuffix("png");
+ fd.selectFile(fn);
+ if (fd.exec() == QDialog::Accepted)
+ img.save(fd.selectedFiles().first());
+}
+
+void MyGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
+{
+ if (!m_initialized)
+ init();
+
+ QOpenGLWidget *w = static_cast<QOpenGLWidget*>(viewport());
+
+ painter->beginNativePainting();
+
+ draw(w->currentTargetBuffer());
+
+ painter->endNativePainting();
+
+
+ m_yaw += 0.5;
+ if (m_yaw > 360)
+ m_yaw = 0;
+}
+
+void MyGraphicsView::resizeEvent(QResizeEvent *event)
+{
+ QGraphicsView::resizeEvent(event);
+
+ resize(event->size().width(), event->size().height());
+}
+
+void MyGraphicsView::init()
+{
+ if (m_initialized)
+ return;
+
+ initializeOpenGLFunctions();
+
+ initShaders();
+ initCube();
+
+ resize(viewport()->width(), viewport()->height());
+
+ m_initialized = true;
+}
+
+void MyGraphicsView::initShaders()
+{
+ static const char *vertexShaderSource =
+ "#ifdef GL_ES\n"
+ "// Set default precision to medium\n"
+ "precision mediump int;\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "attribute vec4 a_position;\n"
+ "uniform mat4 u_mvp;\n"
+ "void main() {\n"
+ "gl_Position = u_mvp * a_position;\n"
+ "}\n";
+
+ static const char *fragmentShaderSource =
+ "#ifdef GL_ES\n"
+ "// Set default precision to medium\n"
+ "precision mediump int;\n"
+ "precision mediump float;\n"
+ "#endif\n"
+ "void main() {\n"
+ "gl_FragColor = vec4(0.7, 0.1, 0.0, 1.0);\n"
+ "}\n";
+
+ if (!m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource))
+ close();
+
+ if (!m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource))
+ close();
+
+ if (!m_program.link())
+ close();
+
+ if (!m_program.bind())
+ close();
+}
+
+void MyGraphicsView::initCube()
+{
+ m_arrayBuf.create();
+ m_indexBuf.create();
+
+ QVector3D vertices[] = {
+ QVector3D(-1.0f, -1.0f, 1.0f),
+ QVector3D( 1.0f, -1.0f, 1.0f),
+ QVector3D(-1.0f, 1.0f, 1.0f),
+ QVector3D( 1.0f, 1.0f, 1.0f),
+ QVector3D( 1.0f, -1.0f, 1.0f),
+ QVector3D( 1.0f, -1.0f, -1.0f),
+ QVector3D( 1.0f, 1.0f, 1.0f),
+ QVector3D( 1.0f, 1.0f, -1.0f),
+ QVector3D( 1.0f, -1.0f, -1.0f),
+ QVector3D(-1.0f, -1.0f, -1.0f),
+ QVector3D( 1.0f, 1.0f, -1.0f),
+ QVector3D(-1.0f, 1.0f, -1.0f),
+ QVector3D(-1.0f, -1.0f, -1.0f),
+ QVector3D(-1.0f, -1.0f, 1.0f),
+ QVector3D(-1.0f, 1.0f, -1.0f),
+ QVector3D(-1.0f, 1.0f, 1.0f),
+ QVector3D(-1.0f, -1.0f, -1.0f),
+ QVector3D( 1.0f, -1.0f, -1.0f),
+ QVector3D(-1.0f, -1.0f, 1.0f),
+ QVector3D( 1.0f, -1.0f, 1.0f),
+ QVector3D(-1.0f, 1.0f, 1.0f),
+ QVector3D( 1.0f, 1.0f, 1.0f),
+ QVector3D(-1.0f, 1.0f, -1.0f),
+ QVector3D( 1.0f, 1.0f, -1.0f)
+ };
+
+ GLushort indices[] = {
+ 0, 1, 2, 3, 3,
+ 4, 4, 5, 6, 7, 7,
+ 8, 8, 9, 10, 11, 11,
+ 12, 12, 13, 14, 15, 15,
+ 16, 16, 17, 18, 19, 19,
+ 20, 20, 21, 22, 23
+ };
+
+
+ m_arrayBuf.bind();
+ m_arrayBuf.allocate(vertices, 24 * sizeof(QVector3D));
+
+ m_indexBuf.bind();
+ m_indexBuf.allocate(indices, 34 * sizeof(GLushort));
+}
+
+void MyGraphicsView::draw(QOpenGLWidget::TargetBuffer targetBuffer)
+{
+ glClearColor(0.0f, 0.7f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ m_program.bind();
+ m_arrayBuf.bind();
+ m_indexBuf.bind();
+
+ QMatrix4x4 matrix;
+ matrix.translate(targetBuffer == QOpenGLWidget::LeftBuffer ? -2.0 : 2.0,
+ 0.0,
+ -10.0);
+ matrix.scale(2.0);
+ matrix.rotate(QQuaternion::fromEulerAngles(10, m_yaw, 0));
+ m_program.setUniformValue("u_mvp", m_projection * matrix);
+
+ int vertexLocation = m_program.attributeLocation("a_position");
+ m_program.enableAttributeArray(vertexLocation);
+ m_program.setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, sizeof(QVector3D));
+
+ glDrawElements(GL_TRIANGLE_STRIP, 34, GL_UNSIGNED_SHORT, nullptr);
+}
+
+void MyGraphicsView::resize(int w, int h)
+{
+ qreal aspect = qreal(w) / qreal(h ? h : 1);
+ const qreal zNear = 0.0, zFar = 100.0, fov = 45.0;
+ m_projection.setToIdentity();
+ m_projection.perspective(fov, aspect, zNear, zFar);
+
+ if (m_initialized)
+ glViewport(0, 0, w, h);
+}
diff --git a/tests/manual/stereographicsview/mygraphicsview.h b/tests/manual/stereographicsview/mygraphicsview.h
new file mode 100644
index 0000000000..03204cb316
--- /dev/null
+++ b/tests/manual/stereographicsview/mygraphicsview.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef MYGRAPHICSVIEW_H
+#define MYGRAPHICSVIEW_H
+
+#include <QOpenGLFunctions>
+#include <QGraphicsView>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLBuffer>
+#include <QMatrix4x4>
+#include <QOpenGLWidget>
+
+class MyGraphicsView : public QGraphicsView, protected QOpenGLFunctions
+{
+public:
+ MyGraphicsView(QWidget *parent = nullptr);
+ MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
+
+ void saveImage(QOpenGLWidget::TargetBuffer targetBuffer);
+
+protected:
+ void drawBackground(QPainter *painter, const QRectF &rect) override;
+
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void init();
+ void initShaders();
+ void initCube();
+
+ void draw(QOpenGLWidget::TargetBuffer targetBuffer);
+
+ void resize(int w, int h);
+
+
+ bool m_initialized = false;
+
+ QOpenGLShaderProgram m_program;
+ QMatrix4x4 m_projection;
+ QOpenGLBuffer m_arrayBuf;
+ QOpenGLBuffer m_indexBuf;
+
+ qreal m_yaw = 0;
+};
+
+#endif // MYGRAPHICSVIEW_H
diff --git a/tests/manual/stereographicsview/stereographicsview.pro b/tests/manual/stereographicsview/stereographicsview.pro
new file mode 100644
index 0000000000..597166367c
--- /dev/null
+++ b/tests/manual/stereographicsview/stereographicsview.pro
@@ -0,0 +1,13 @@
+QT += widgets widgets-private gui-private core-private
+
+TARGET = stereographicsview
+TEMPLATE = app
+
+SOURCES += main.cpp \
+ mainwindow.cpp \
+ mainwindow.h \
+ mainwindow.ui \
+ mygraphicsview.h \
+ mygraphicsview.cpp \
+
+HEADERS += openglwidget.h