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:
parent
9bc74d14f3
commit
c450f6d21c
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
32
tests/manual/stereographicsview/CMakeLists.txt
Normal file
32
tests/manual/stereographicsview/CMakeLists.txt
Normal 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"
|
26
tests/manual/stereographicsview/main.cpp
Normal file
26
tests/manual/stereographicsview/main.cpp
Normal 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"
|
79
tests/manual/stereographicsview/mainwindow.cpp
Normal file
79
tests/manual/stereographicsview/mainwindow.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
30
tests/manual/stereographicsview/mainwindow.h
Normal file
30
tests/manual/stereographicsview/mainwindow.h
Normal 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
|
101
tests/manual/stereographicsview/mainwindow.ui
Normal file
101
tests/manual/stereographicsview/mainwindow.ui
Normal 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>
|
209
tests/manual/stereographicsview/mygraphicsview.cpp
Normal file
209
tests/manual/stereographicsview/mygraphicsview.cpp
Normal 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);
|
||||||
|
}
|
47
tests/manual/stereographicsview/mygraphicsview.h
Normal file
47
tests/manual/stereographicsview/mygraphicsview.h
Normal 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
|
13
tests/manual/stereographicsview/stereographicsview.pro
Normal file
13
tests/manual/stereographicsview/stereographicsview.pro
Normal 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
|
Loading…
x
Reference in New Issue
Block a user