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 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;
}
/*!

View File

@ -45,6 +45,7 @@ public:
bool isValid() const;
void makeCurrent();
void makeCurrent(TargetBuffer targetBuffer);
void doneCurrent();
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
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();

View File

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

View File

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

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