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>
This commit is contained in:
Kristoffer Skau 2022-11-24 14:31:09 +01:00
parent 9bc74d14f3
commit c450f6d21c
13 changed files with 766 additions and 134 deletions

View File

@ -552,7 +552,7 @@ public:
void destroyFbos(); void destroyFbos();
void setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer); bool setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
QImage grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer); QImage grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer);
QImage grabFramebuffer() override; QImage grabFramebuffer() override;
void beginBackingStorePainting() override { inBackingStorePaint = true; } void beginBackingStorePainting() override { inBackingStorePaint = true; }
@ -560,9 +560,13 @@ public:
void beginCompose() override; void beginCompose() override;
void endCompose() override; void endCompose() override;
void initializeViewportFramebuffer() override; void initializeViewportFramebuffer() override;
bool isStereoEnabled() override;
bool toggleStereoTargetBuffer() override;
void resizeViewportFramebuffer() override; void resizeViewportFramebuffer() override;
void resolveSamples() override; void resolveSamples() override;
void resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
QOpenGLContext *context = nullptr; QOpenGLContext *context = nullptr;
QRhiTexture *wrapperTextures[2] = {}; QRhiTexture *wrapperTextures[2] = {};
QOpenGLFramebufferObject *fbos[2] = {}; QOpenGLFramebufferObject *fbos[2] = {};
@ -697,8 +701,6 @@ void QOpenGLWidgetPrivate::reset()
void QOpenGLWidgetPrivate::resetRhiDependentResources() void QOpenGLWidgetPrivate::resetRhiDependentResources()
{ {
Q_Q(QOpenGLWidget);
// QRhi resource created from the QRhi. These must be released whenever the // QRhi resource created from the QRhi. These must be released whenever the
// widget gets associated with a different QRhi, even when all OpenGL // widget gets associated with a different QRhi, even when all OpenGL
// contexts share resources. // contexts share resources.
@ -706,7 +708,7 @@ void QOpenGLWidgetPrivate::resetRhiDependentResources()
delete wrapperTextures[0]; delete wrapperTextures[0];
wrapperTextures[0] = nullptr; wrapperTextures[0] = nullptr;
if (q->format().stereo()) { if (isStereoEnabled()) {
delete wrapperTextures[1]; delete wrapperTextures[1];
wrapperTextures[1] = nullptr; wrapperTextures[1] = nullptr;
} }
@ -738,9 +740,9 @@ void QOpenGLWidgetPrivate::recreateFbos()
if (samples > 0) if (samples > 0)
resolvedFbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize); 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); fbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
if (samples > 0) if (samples > 0)
resolvedFbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize); 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); context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
ensureRhiDependentResources(); ensureRhiDependentResources();
if (stereoEnabled) { if (stereo) {
currentTargetBuffer = QOpenGLWidget::RightBuffer; currentTargetBuffer = QOpenGLWidget::RightBuffer;
fbos[currentTargetBuffer]->bind(); fbos[currentTargetBuffer]->bind();
context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
ensureRhiDependentResources(); ensureRhiDependentResources();
currentTargetBuffer = QOpenGLWidget::LeftBuffer;
} }
flushPending = true; // Make sure the FBO is initialized before use flushPending = true; // Make sure the FBO is initialized before use
@ -894,12 +897,18 @@ void QOpenGLWidgetPrivate::initialize()
} }
void QOpenGLWidgetPrivate::resolveSamples() void QOpenGLWidgetPrivate::resolveSamples()
{
resolveSamplesForBuffer(QOpenGLWidget::LeftBuffer);
resolveSamplesForBuffer(QOpenGLWidget::RightBuffer);
}
void QOpenGLWidgetPrivate::resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
{ {
Q_Q(QOpenGLWidget); Q_Q(QOpenGLWidget);
if (resolvedFbos[currentTargetBuffer]) { if (resolvedFbos[targetBuffer]) {
q->makeCurrent(); q->makeCurrent(targetBuffer);
QRect rect(QPoint(0, 0), fbos[currentTargetBuffer]->size()); QRect rect(QPoint(0, 0), fbos[targetBuffer]->size());
QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[currentTargetBuffer], rect, fbos[currentTargetBuffer], rect); QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[targetBuffer], rect, fbos[targetBuffer], rect);
flushPending = true; flushPending = true;
} }
} }
@ -924,8 +933,8 @@ void QOpenGLWidgetPrivate::render()
return; return;
} }
const bool stereoEnabled = q->format().stereo(); const bool stereo = isStereoEnabled();
if (stereoEnabled) { if (stereo) {
static bool warningGiven = false; static bool warningGiven = false;
if (!fbos[QOpenGLWidget::RightBuffer] && !warningGiven) { if (!fbos[QOpenGLWidget::RightBuffer] && !warningGiven) {
qWarning("QOpenGLWidget: Stereo is enabled, but no right buffer. Using only left buffer"); 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) { if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
invalidateFbo(); invalidateFbo();
if (stereoEnabled && fbos[QOpenGLWidget::RightBuffer]) { if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
setCurrentTargetBuffer(QOpenGLWidget::RightBuffer); setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
invalidateFbo(); invalidateFbo();
setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer); setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
@ -952,7 +961,7 @@ void QOpenGLWidgetPrivate::render()
QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle(); QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
q->paintGL(); q->paintGL();
if (stereoEnabled && fbos[QOpenGLWidget::RightBuffer]) { if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
setCurrentTargetBuffer(QOpenGLWidget::RightBuffer); setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle(); QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
q->paintGL(); q->paintGL();
@ -1017,7 +1026,7 @@ QImage QOpenGLWidgetPrivate::grabFramebuffer(QOpenGLWidget::TargetBuffer targetB
// The second fbo is only created when stereoscopic rendering is enabled // The second fbo is only created when stereoscopic rendering is enabled
// Just use the default one if not. // Just use the default one if not.
if (targetBuffer == QOpenGLWidget::RightBuffer && !q->format().stereo()) if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
targetBuffer = QOpenGLWidget::LeftBuffer; targetBuffer = QOpenGLWidget::LeftBuffer;
if (!fbos[targetBuffer]) // could be completely offscreen, without ever getting a resize event 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); setCurrentTargetBuffer(targetBuffer);
if (resolvedFbos[targetBuffer]) { if (resolvedFbos[targetBuffer]) {
resolveSamples(); resolveSamplesForBuffer(targetBuffer);
resolvedFbos[targetBuffer]->bind(); resolvedFbos[targetBuffer]->bind();
} }
@ -1054,6 +1063,22 @@ void QOpenGLWidgetPrivate::initializeViewportFramebuffer()
q->setAutoFillBackground(true); 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() void QOpenGLWidgetPrivate::resizeViewportFramebuffer()
{ {
Q_Q(QOpenGLWidget); Q_Q(QOpenGLWidget);
@ -1253,6 +1278,34 @@ void QOpenGLWidget::makeCurrent()
d->fbos[d->currentTargetBuffer]->bind(); d->fbos[d->currentTargetBuffer]->bind();
} }
/*!
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. Releases the context.
@ -1585,11 +1638,18 @@ QPaintEngine *QOpenGLWidget::paintEngine() const
return d->paintDevice->paintEngine(); return d->paintDevice->paintEngine();
} }
void QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
bool QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
{ {
Q_Q(QOpenGLWidget); Q_Q(QOpenGLWidget);
if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
return false;
currentTargetBuffer = targetBuffer; currentTargetBuffer = targetBuffer;
q->makeCurrent(); q->makeCurrent();
return true;
} }
/*! /*!

View File

@ -45,6 +45,7 @@ public:
bool isValid() const; bool isValid() const;
void makeCurrent(); void makeCurrent();
void makeCurrent(TargetBuffer targetBuffer);
void doneCurrent(); void doneCurrent();
QOpenGLContext *context() const; QOpenGLContext *context() const;

View File

@ -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 view coordinates and scene coordinates, and to find items on the scene
using view coordinates. 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 \image graphicsview-view.png
\note Using an OpenGL viewport limits the ability to use QGraphicsProxyWidget. \note Using an OpenGL viewport limits the ability to use QGraphicsProxyWidget.
@ -2724,6 +2734,9 @@ void QGraphicsView::setupViewport(QWidget *widget)
widget->setFocusPolicy(Qt::StrongFocus); widget->setFocusPolicy(Qt::StrongFocus);
if (isGLWidget)
d->stereoEnabled = QWidgetPrivate::get(widget)->isStereoEnabled();
if (!isGLWidget) { if (!isGLWidget) {
// autoFillBackground enables scroll acceleration. // autoFillBackground enables scroll acceleration.
widget->setAutoFillBackground(true); widget->setAutoFillBackground(true);
@ -3425,132 +3438,146 @@ void QGraphicsView::paintEvent(QPaintEvent *event)
painter.setWorldTransform(viewportTransform()); painter.setWorldTransform(viewportTransform());
const QTransform viewTransform = painter.worldTransform(); const QTransform viewTransform = painter.worldTransform();
// Draw background const auto actuallyDraw = [&]() {
if (d->cacheMode & CacheBackground) { // Draw background
// Recreate the background pixmap, and flag the whole background as if (d->cacheMode & CacheBackground) {
// exposed. // Recreate the background pixmap, and flag the whole background as
if (d->mustResizeBackgroundPixmap) { // exposed.
const qreal dpr = d->viewport->devicePixelRatio(); if (d->mustResizeBackgroundPixmap) {
d->backgroundPixmap = QPixmap(viewport()->size() * dpr); const qreal dpr = d->viewport->devicePixelRatio();
d->backgroundPixmap.setDevicePixelRatio(dpr); d->backgroundPixmap = QPixmap(viewport()->size() * dpr);
QBrush bgBrush = viewport()->palette().brush(viewport()->backgroundRole()); d->backgroundPixmap.setDevicePixelRatio(dpr);
if (!bgBrush.isOpaque()) QBrush bgBrush = viewport()->palette().brush(viewport()->backgroundRole());
d->backgroundPixmap.fill(Qt::transparent); if (!bgBrush.isOpaque())
QPainter p(&d->backgroundPixmap); d->backgroundPixmap.fill(Qt::transparent);
p.fillRect(0, 0, d->backgroundPixmap.width(), d->backgroundPixmap.height(), bgBrush); QPainter p(&d->backgroundPixmap);
d->backgroundPixmapExposed = QRegion(viewport()->rect()); p.fillRect(0, 0, d->backgroundPixmap.width(), d->backgroundPixmap.height(), bgBrush);
d->mustResizeBackgroundPixmap = false; 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();
}
// 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 {
if (!(d->optimizationFlags & DontSavePainterState))
painter.save();
drawBackground(&painter, exposedSceneRect);
if (!(d->optimizationFlags & DontSavePainterState))
painter.restore();
} }
// Redraw exposed areas // Items
if (!d->backgroundPixmapExposed.isEmpty()) { if (!(d->optimizationFlags & IndirectPainting)) {
QPainter backgroundPainter(&d->backgroundPixmap); const quint32 oldRectAdjust = d->scene->d_func()->rectAdjust;
backgroundPainter.setClipRegion(d->backgroundPixmapExposed, Qt::ReplaceClip); if (d->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing)
if (viewTransformed) d->scene->d_func()->rectAdjust = 1;
backgroundPainter.setTransform(viewTransform); else
QRectF backgroundExposedSceneRect = mapToScene(d->backgroundPixmapExposed.boundingRect()).boundingRect(); d->scene->d_func()->rectAdjust = 2;
drawBackground(&backgroundPainter, backgroundExposedSceneRect); d->scene->d_func()->drawItems(&painter, viewTransformed ? &viewTransform : nullptr,
d->backgroundPixmapExposed = QRegion(); &d->exposedRegion, viewport());
} d->scene->d_func()->rectAdjust = oldRectAdjust;
// Make sure the painter's world transform is restored correctly when
// Blit the background from the background pixmap // drawing without painter state protection (DontSavePainterState).
if (viewTransformed) { // We only change the worldTransform() so there's no need to do a full-blown
painter.setWorldTransform(QTransform()); // save() and restore(). Also note that we don't have to do this in case of
painter.drawPixmap(QPoint(), d->backgroundPixmap); // 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); painter.setWorldTransform(viewTransform);
} else { } else {
painter.drawPixmap(QPoint(), d->backgroundPixmap); // Make sure we don't have unpolished items before we draw
} if (!d->scene->d_func()->unpolishedItems.isEmpty())
} else { d->scene->d_func()->_q_polishItems();
if (!(d->optimizationFlags & DontSavePainterState)) // We reset updateAll here (after we've issued polish events)
painter.save(); // so that we can discard update requests coming from polishEvent().
drawBackground(&painter, exposedSceneRect); d->scene->d_func()->updateAll = false;
if (!(d->optimizationFlags & DontSavePainterState))
painter.restore();
}
// Items // Find all exposed items
if (!(d->optimizationFlags & IndirectPainting)) { bool allItems = false;
const quint32 oldRectAdjust = d->scene->d_func()->rectAdjust; QList<QGraphicsItem *> itemList = d->findItems(d->exposedRegion, &allItems, viewTransform);
if (d->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing) if (!itemList.isEmpty()) {
d->scene->d_func()->rectAdjust = 1; // Generate the style options.
else const int numItems = itemList.size();
d->scene->d_func()->rectAdjust = 2; QGraphicsItem **itemArray = &itemList[0]; // Relies on QList internals, but is perfectly valid.
d->scene->d_func()->drawItems(&painter, viewTransformed ? &viewTransform : nullptr, QStyleOptionGraphicsItem *styleOptionArray = d->allocStyleOptionsArray(numItems);
&d->exposedRegion, viewport()); QTransform transform(Qt::Uninitialized);
d->scene->d_func()->rectAdjust = oldRectAdjust; for (int i = 0; i < numItems; ++i) {
// Make sure the painter's world transform is restored correctly when QGraphicsItem *item = itemArray[i];
// drawing without painter state protection (DontSavePainterState). QGraphicsItemPrivate *itemd = item->d_ptr.data();
// We only change the worldTransform() so there's no need to do a full-blown itemd->initStyleOption(&styleOptionArray[i], viewTransform, d->exposedRegion, allItems);
// save() and restore(). Also note that we don't have to do this in case of // Cache the item's area in view coordinates.
// IndirectPainting (the else branch), because in that case we always save() // Note that we have to do this here in case the base class implementation
// and restore() in QGraphicsScene::drawItems(). // (QGraphicsScene::drawItems) is not called. If it is, we'll do this
if (!d->scene->d_func()->painterStateProtection) // operation twice, but that's the price one has to pay for using indirect
painter.setOpacity(1.0); // painting :-/.
painter.setWorldTransform(viewTransform); const QRectF brect = adjustedItemEffectiveBoundingRect(item);
} else { if (!itemd->itemIsUntransformable()) {
// Make sure we don't have unpolished items before we draw transform = item->sceneTransform();
if (!d->scene->d_func()->unpolishedItems.isEmpty()) if (viewTransformed)
d->scene->d_func()->_q_polishItems(); transform *= viewTransform;
// We reset updateAll here (after we've issued polish events) } else {
// so that we can discard update requests coming from polishEvent(). transform = item->deviceTransform(viewTransform);
d->scene->d_func()->updateAll = false; }
itemd->paintedViewBoundingRects.insert(d->viewport, transform.mapRect(brect).toRect());
// 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()); // 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);
#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()); // 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);
}
viewport()->style()->drawControl(QStyle::CE_RubberBand, &option, &painter, viewport());
}
#endif
};
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(); painter.end();

View File

@ -100,6 +100,8 @@ public:
QGraphicsView::ViewportUpdateMode viewportUpdateMode; QGraphicsView::ViewportUpdateMode viewportUpdateMode;
QGraphicsView::OptimizationFlags optimizationFlags; QGraphicsView::OptimizationFlags optimizationFlags;
bool stereoEnabled = false; // Set in setupViewport()
QPointer<QGraphicsScene> scene; QPointer<QGraphicsScene> scene;
#if QT_CONFIG(rubberband) #if QT_CONFIG(rubberband)
QRect rubberBandRect; QRect rubberBandRect;

View File

@ -621,6 +621,11 @@ public:
// Called after each paint event. // Called after each paint event.
virtual void resolveSamples() { } 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); static void setWidgetParentHelper(QObject *widgetAsObject, QObject *newParent);
std::string flagsForDumping() const override; std::string flagsForDumping() const override;

View File

@ -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"

View File

@ -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"

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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