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 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
|
||||
@ -894,12 +897,18 @@ 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);
|
||||
@ -1253,6 +1278,34 @@ void QOpenGLWidget::makeCurrent()
|
||||
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.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -45,6 +45,7 @@ public:
|
||||
bool isValid() const;
|
||||
|
||||
void makeCurrent();
|
||||
void makeCurrent(TargetBuffer targetBuffer);
|
||||
void doneCurrent();
|
||||
|
||||
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
|
||||
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,6 +3438,7 @@ void QGraphicsView::paintEvent(QPaintEvent *event)
|
||||
painter.setWorldTransform(viewportTransform());
|
||||
const QTransform viewTransform = painter.worldTransform();
|
||||
|
||||
const auto actuallyDraw = [&]() {
|
||||
// Draw background
|
||||
if (d->cacheMode & CacheBackground) {
|
||||
// Recreate the background pixmap, and flag the whole background as
|
||||
@ -3464,6 +3478,7 @@ void QGraphicsView::paintEvent(QPaintEvent *event)
|
||||
} else {
|
||||
if (!(d->optimizationFlags & DontSavePainterState))
|
||||
painter.save();
|
||||
|
||||
drawBackground(&painter, exposedSceneRect);
|
||||
if (!(d->optimizationFlags & DontSavePainterState))
|
||||
painter.restore();
|
||||
@ -3551,6 +3566,18 @@ void QGraphicsView::paintEvent(QPaintEvent *event)
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
painter.end();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
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