summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKristoffer Skau <kristoffer.skau@qt.io>2022-10-27 15:38:53 +0200
committerKristoffer Skau <kristoffer.skau@qt.io>2022-11-28 19:12:27 +0100
commitee2dbcada81f5220a05414d7bf9d5eeebdda8972 (patch)
tree2bca1fc0c3569a98f26a0717d9e555a9b88f2364
parent8155bd54261688f333b2d68e2d76f0fc076a0fb4 (diff)
Add support for stereoscopic content in QOpenGLWidget
Need to add the plumbing necessary to support two textures in QOpenGLWidget and use these in the backing store. The changes required on the RHI level is already done in an earlier patch. Then paintGL() needs to be called twice, once for each buffer. Also add overloads for the other functions of QOopenGLWidget where it makes sense to query for left or right buffer. Then finally create an example. [ChangeLog][Widgets][QOpenGLWidget] Added support for stereoscopic rendering. Fixes: QTBUG-64587 Change-Id: I5a5c53506dcf8a56442097290dceb7eb730d50ce Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
-rw-r--r--examples/opengl/CMakeLists.txt1
-rw-r--r--examples/opengl/doc/images/stereoexample-leftbuffer.pngbin0 -> 9987 bytes
-rw-r--r--examples/opengl/doc/images/stereoexample-rightbuffer.pngbin0 -> 10002 bytes
-rw-r--r--examples/opengl/doc/src/stereoqopenglwidget.qdoc41
-rw-r--r--examples/opengl/opengl.pro3
-rw-r--r--examples/opengl/stereoqopenglwidget/CMakeLists.txt41
-rw-r--r--examples/opengl/stereoqopenglwidget/glwidget.cpp277
-rw-r--r--examples/opengl/stereoqopenglwidget/glwidget.h53
-rw-r--r--examples/opengl/stereoqopenglwidget/main.cpp31
-rw-r--r--examples/opengl/stereoqopenglwidget/mainwindow.cpp25
-rw-r--r--examples/opengl/stereoqopenglwidget/mainwindow.h21
-rw-r--r--examples/opengl/stereoqopenglwidget/stereoqopenglwidget.pro11
-rw-r--r--src/gui/painting/qbackingstoredefaultcompositor.cpp128
-rw-r--r--src/gui/painting/qbackingstoredefaultcompositor_p.h11
-rw-r--r--src/gui/painting/qplatformbackingstore.cpp23
-rw-r--r--src/gui/painting/qplatformbackingstore.h4
-rw-r--r--src/gui/rhi/qrhi.cpp6
-rw-r--r--src/openglwidgets/qopenglwidget.cpp296
-rw-r--r--src/openglwidgets/qopenglwidget.h9
-rw-r--r--src/widgets/kernel/qwidget_p.h8
-rw-r--r--src/widgets/kernel/qwidgetrepaintmanager.cpp3
21 files changed, 886 insertions, 106 deletions
diff --git a/examples/opengl/CMakeLists.txt b/examples/opengl/CMakeLists.txt
index 9c6768f324..d2c7ece2fd 100644
--- a/examples/opengl/CMakeLists.txt
+++ b/examples/opengl/CMakeLists.txt
@@ -15,4 +15,5 @@ if(TARGET Qt6::Widgets)
qt_internal_add_example(textures)
qt_internal_add_example(hellogles3)
qt_internal_add_example(computegles31)
+ qt_internal_add_example(stereoqopenglwidget)
endif()
diff --git a/examples/opengl/doc/images/stereoexample-leftbuffer.png b/examples/opengl/doc/images/stereoexample-leftbuffer.png
new file mode 100644
index 0000000000..6ae7d5118b
--- /dev/null
+++ b/examples/opengl/doc/images/stereoexample-leftbuffer.png
Binary files differ
diff --git a/examples/opengl/doc/images/stereoexample-rightbuffer.png b/examples/opengl/doc/images/stereoexample-rightbuffer.png
new file mode 100644
index 0000000000..3e2dc3c671
--- /dev/null
+++ b/examples/opengl/doc/images/stereoexample-rightbuffer.png
Binary files differ
diff --git a/examples/opengl/doc/src/stereoqopenglwidget.qdoc b/examples/opengl/doc/src/stereoqopenglwidget.qdoc
new file mode 100644
index 0000000000..c4e51f9cee
--- /dev/null
+++ b/examples/opengl/doc/src/stereoqopenglwidget.qdoc
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example stereoqopenglwidget
+ \title QOpenGLWidget Stereoscopic Rendering Example
+
+ \brief This example shows how to create a minimal QOpenGLWidget based application
+ with stereoscopic rendering support.
+
+ \image stereoexample-leftbuffer.png
+
+ The above image is what will be rendered to the left buffer.
+
+ \image stereoexample-rightbuffer.png
+
+ The above image is what will be rendered to the right buffer.
+
+ \note Support for stereoscopic rendering has certain hardware requirements, like
+ your graphics card needs stereo support.
+
+ \section1 Setting the correct surface flag
+ To enable stereoscopic rendering you need to set the flag
+ QSurfaceFormat::StereoBuffers globally. Just doing it on the widget is not enough
+ because of how the flag is handled internally. The safest is to do it on
+ QSurfaceFormat::SetDefaultFormat prior to starting the application.
+
+ \snippet stereoqopenglwidget/main.cpp 1
+
+ \section1 Rendering twice
+ After QSurfaceFormat::StereoBuffers is set, then paintGL() will be called twice,
+ once for each buffer. In paintGL() you can call currentTargetBuffer() to query
+ which TargetBuffer is currently active.
+
+ In the following snippet we slightly translate the matrix to not render the
+ vertices on top of each other. This is a simple example just too see that if the
+ necessary support is there, at runtime you should see two objects, one on the left
+ and one on the right.
+
+ \snippet stereoqopenglwidget/glwidget.cpp 1
+*/
diff --git a/examples/opengl/opengl.pro b/examples/opengl/opengl.pro
index 907930d7ac..24cda20f63 100644
--- a/examples/opengl/opengl.pro
+++ b/examples/opengl/opengl.pro
@@ -14,5 +14,6 @@ qtHaveModule(widgets) {
cube \
textures \
hellogles3 \
- computegles31
+ computegles31 \
+ stereoqopenglwidget
}
diff --git a/examples/opengl/stereoqopenglwidget/CMakeLists.txt b/examples/opengl/stereoqopenglwidget/CMakeLists.txt
new file mode 100644
index 0000000000..69fd304bb2
--- /dev/null
+++ b/examples/opengl/stereoqopenglwidget/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(stereoqopenglwidget LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/opengl/stereoqopenglwidget")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL OpenGLWidgets Widgets)
+
+qt_add_executable(stereoqopenglwidget
+ glwidget.cpp glwidget.h
+ main.cpp
+ mainwindow.cpp mainwindow.h
+)
+
+set_target_properties(stereoqopenglwidget PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(stereoqopenglwidget PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::OpenGL
+ Qt::OpenGLWidgets
+ Qt::Widgets
+)
+
+
+install(TARGETS stereoqopenglwidget
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/opengl/stereoqopenglwidget/glwidget.cpp b/examples/opengl/stereoqopenglwidget/glwidget.cpp
new file mode 100644
index 0000000000..36a6300348
--- /dev/null
+++ b/examples/opengl/stereoqopenglwidget/glwidget.cpp
@@ -0,0 +1,277 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "glwidget.h"
+#include <QPainter>
+#include <QPaintEngine>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLTexture>
+#include <QRandomGenerator>
+#include <QCoreApplication>
+#include <QFileDialog>
+#include <qmath.h>
+
+GLWidget::GLWidget(const QColor &background)
+ : m_background(background)
+{
+ setMinimumSize(300, 250);
+}
+
+GLWidget::~GLWidget()
+{
+ reset();
+}
+
+void GLWidget::saveImage(TargetBuffer targetBuffer)
+{
+ QImage img = grabFramebuffer(targetBuffer);
+ if (img.isNull()) {
+ qFatal("Failed to grab framebuffer");
+ }
+
+ const char *fn =
+ targetBuffer == TargetBuffer::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 GLWidget::reset()
+{
+ // And now release all OpenGL resources.
+ makeCurrent();
+ delete m_program;
+ m_program = nullptr;
+ delete m_vshader;
+ m_vshader = nullptr;
+ delete m_fshader;
+ m_fshader = nullptr;
+ m_vbo.destroy();
+ doneCurrent();
+
+ // We are done with the current QOpenGLContext, forget it. If there is a
+ // subsequent initialize(), that will then connect to the new context.
+ QObject::disconnect(m_contextWatchConnection);
+}
+void GLWidget::initializeGL()
+{
+ initializeOpenGLFunctions();
+
+ m_vshader = new QOpenGLShader(QOpenGLShader::Vertex);
+ const char *vsrc1 =
+ "attribute highp vec4 vertex;\n"
+ "attribute mediump vec3 normal;\n"
+ "uniform mediump mat4 matrix;\n"
+ "varying mediump vec4 color;\n"
+ "void main(void)\n"
+ "{\n"
+ " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n"
+ " float angle = max(dot(normal, toLight), 0.0);\n"
+ " vec3 col = vec3(0.40, 1.0, 0.0);\n"
+ " color = vec4(col * 0.2 + col * 0.8 * angle, 1.0);\n"
+ " color = clamp(color, 0.0, 1.0);\n"
+ " gl_Position = matrix * vertex;\n"
+ "}\n";
+ m_vshader->compileSourceCode(vsrc1);
+
+ m_fshader = new QOpenGLShader(QOpenGLShader::Fragment);
+ const char *fsrc1 =
+ "varying mediump vec4 color;\n"
+ "void main(void)\n"
+ "{\n"
+ " gl_FragColor = color;\n"
+ "}\n";
+ m_fshader->compileSourceCode(fsrc1);
+
+ m_program = new QOpenGLShaderProgram;
+ m_program->addShader(m_vshader);
+ m_program->addShader(m_fshader);
+ m_program->link();
+
+
+ m_vertexAttr = m_program->attributeLocation("vertex");
+ m_normalAttr = m_program->attributeLocation("normal");
+ m_matrixUniform = m_program->uniformLocation("matrix");
+
+ createGeometry();
+
+ m_vbo.create();
+ m_vbo.bind();
+ const int vertexCount = m_vertices.count();
+ QList<GLfloat> buf;
+ buf.resize(vertexCount * 3 * 2);
+ GLfloat *p = buf.data();
+ for (int i = 0; i < vertexCount; ++i) {
+ *p++ = m_vertices[i].x();
+ *p++ = m_vertices[i].y();
+ *p++ = m_vertices[i].z();
+ *p++ = m_normals[i].x();
+ *p++ = m_normals[i].y();
+ *p++ = m_normals[i].z();
+ }
+ m_vbo.allocate(buf.constData(), (int)buf.count() * sizeof(GLfloat));
+ m_vbo.release();
+
+ m_contextWatchConnection = QObject::connect(context(), &QOpenGLContext::aboutToBeDestroyed, context(), [this] { reset(); });
+
+ glFrontFace(GL_CW);
+ glCullFace(GL_FRONT);
+ glEnable(GL_CULL_FACE);
+ glEnable(GL_DEPTH_TEST);
+}
+
+void GLWidget::paintGL()
+{
+ // When QSurfaceFormat::StereoBuffers is enabled, this function is called twice.
+ // Once where currentTargetBuffer() == QOpenGLWidget::LeftBuffer,
+ // and once where currentTargetBuffer() == QOpenGLWidget::RightBuffer.
+
+ glClearColor(m_background.redF(), m_background.greenF(), m_background.blueF(), 1.0f);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ //! [1]
+ // Slightly translate the model, so that there's a visible difference in each buffer.
+ QMatrix4x4 modelview;
+ if (currentTargetBuffer() == QOpenGLWidget::LeftBuffer)
+ modelview.translate(-0.4f, 0.0f, 0.0f);
+ else if (currentTargetBuffer() == QOpenGLWidget::RightBuffer)
+ modelview.translate(0.4f, 0.0f, 0.0f);
+ //! [1]
+
+ m_program->bind();
+ m_program->setUniformValue(m_matrixUniform, modelview);
+ m_program->enableAttributeArray(m_vertexAttr);
+ m_program->enableAttributeArray(m_normalAttr);
+
+ m_vbo.bind();
+ m_program->setAttributeBuffer(m_vertexAttr, GL_FLOAT, 0, 3, 6 * sizeof(GLfloat));
+ m_program->setAttributeBuffer(m_normalAttr, GL_FLOAT, 3 * sizeof(GLfloat), 3, 6 * sizeof(GLfloat));
+ m_vbo.release();
+
+ glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
+
+ m_program->disableAttributeArray(m_normalAttr);
+ m_program->disableAttributeArray(m_vertexAttr);
+ m_program->release();
+ update();
+}
+
+void GLWidget::createGeometry()
+{
+ m_vertices.clear();
+ m_normals.clear();
+
+ qreal x1 = +0.06f;
+ qreal y1 = -0.14f;
+ qreal x2 = +0.14f;
+ qreal y2 = -0.06f;
+ qreal x3 = +0.08f;
+ qreal y3 = +0.00f;
+ qreal x4 = +0.30f;
+ qreal y4 = +0.22f;
+
+ quad(x1, y1, x2, y2, y2, x2, y1, x1);
+ quad(x3, y3, x4, y4, y4, x4, y3, x3);
+
+ extrude(x1, y1, x2, y2);
+ extrude(x2, y2, y2, x2);
+ extrude(y2, x2, y1, x1);
+ extrude(y1, x1, x1, y1);
+ extrude(x3, y3, x4, y4);
+ extrude(x4, y4, y4, x4);
+ extrude(y4, x4, y3, x3);
+
+ const int NumSectors = 100;
+ const qreal sectorAngle = 2 * qreal(M_PI) / NumSectors;
+
+ for (int i = 0; i < NumSectors; ++i) {
+ qreal angle = i * sectorAngle;
+ qreal x5 = 0.30 * sin(angle);
+ qreal y5 = 0.30 * cos(angle);
+ qreal x6 = 0.20 * sin(angle);
+ qreal y6 = 0.20 * cos(angle);
+
+ angle += sectorAngle;
+ qreal x7 = 0.20 * sin(angle);
+ qreal y7 = 0.20 * cos(angle);
+ qreal x8 = 0.30 * sin(angle);
+ qreal y8 = 0.30 * cos(angle);
+
+ quad(x5, y5, x6, y6, x7, y7, x8, y8);
+
+ extrude(x6, y6, x7, y7);
+ extrude(x8, y8, x5, y5);
+ }
+
+ for (int i = 0;i < m_vertices.size();i++)
+ m_vertices[i] *= 2.0f;
+}
+
+void GLWidget::quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4)
+{
+ m_vertices << QVector3D(x1, y1, -0.05f);
+ m_vertices << QVector3D(x2, y2, -0.05f);
+ m_vertices << QVector3D(x4, y4, -0.05f);
+
+ m_vertices << QVector3D(x3, y3, -0.05f);
+ m_vertices << QVector3D(x4, y4, -0.05f);
+ m_vertices << QVector3D(x2, y2, -0.05f);
+
+ QVector3D n = QVector3D::normal
+ (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(x4 - x1, y4 - y1, 0.0f));
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_vertices << QVector3D(x4, y4, 0.05f);
+ m_vertices << QVector3D(x2, y2, 0.05f);
+ m_vertices << QVector3D(x1, y1, 0.05f);
+
+ m_vertices << QVector3D(x2, y2, 0.05f);
+ m_vertices << QVector3D(x4, y4, 0.05f);
+ m_vertices << QVector3D(x3, y3, 0.05f);
+
+ n = QVector3D::normal
+ (QVector3D(x2 - x4, y2 - y4, 0.0f), QVector3D(x1 - x4, y1 - y4, 0.0f));
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+}
+
+void GLWidget::extrude(qreal x1, qreal y1, qreal x2, qreal y2)
+{
+ m_vertices << QVector3D(x1, y1, +0.05f);
+ m_vertices << QVector3D(x2, y2, +0.05f);
+ m_vertices << QVector3D(x1, y1, -0.05f);
+
+ m_vertices << QVector3D(x2, y2, -0.05f);
+ m_vertices << QVector3D(x1, y1, -0.05f);
+ m_vertices << QVector3D(x2, y2, +0.05f);
+
+ QVector3D n = QVector3D::normal
+ (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(0.0f, 0.0f, -0.1f));
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+}
diff --git a/examples/opengl/stereoqopenglwidget/glwidget.h b/examples/opengl/stereoqopenglwidget/glwidget.h
new file mode 100644
index 0000000000..0014ee37c1
--- /dev/null
+++ b/examples/opengl/stereoqopenglwidget/glwidget.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef GLWIDGET_H
+#define GLWIDGET_H
+
+#include <QOpenGLWidget>
+#include <QOpenGLFunctions>
+#include <QOpenGLBuffer>
+#include <QVector3D>
+#include <QMatrix4x4>
+#include <QElapsedTimer>
+#include <QList>
+#include <QPushButton>
+
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLTexture)
+QT_FORWARD_DECLARE_CLASS(QOpenGLShader)
+QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
+
+class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
+{
+ Q_OBJECT
+public:
+ GLWidget(const QColor &background);
+ ~GLWidget();
+
+ void saveImage(QOpenGLWidget::TargetBuffer targetBuffer);
+
+protected:
+ void paintGL() override;
+ void initializeGL() override;
+
+private:
+ void createGeometry();
+ void quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4);
+ void extrude(qreal x1, qreal y1, qreal x2, qreal y2);
+ void reset();
+
+ QList<QVector3D> m_vertices;
+ QList<QVector3D> m_normals;
+ QOpenGLShader *m_vshader = nullptr;
+ QOpenGLShader *m_fshader = nullptr;
+ QOpenGLShaderProgram *m_program = nullptr;
+ QOpenGLBuffer m_vbo;
+ int m_vertexAttr;
+ int m_normalAttr;
+ int m_matrixUniform;
+ QColor m_background;
+ QMetaObject::Connection m_contextWatchConnection;
+};
+
+#endif
diff --git a/examples/opengl/stereoqopenglwidget/main.cpp b/examples/opengl/stereoqopenglwidget/main.cpp
new file mode 100644
index 0000000000..8aad756eca
--- /dev/null
+++ b/examples/opengl/stereoqopenglwidget/main.cpp
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+#include <QSurfaceFormat>
+#include "mainwindow.h"
+
+int main( int argc, char ** argv )
+{
+ QApplication a( argc, argv );
+
+ QCoreApplication::setApplicationName("Qt QOpenGLWidget Stereoscopic Rendering Example");
+ QCoreApplication::setOrganizationName("QtProject");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+
+ //! [1]
+ QSurfaceFormat format;
+ format.setDepthBufferSize(24);
+ format.setStencilBufferSize(8);
+
+ // Enable stereoscopic rendering support
+ format.setStereo(true);
+
+ QSurfaceFormat::setDefaultFormat(format);
+ //! [1]
+
+ MainWindow mw;
+ mw.resize(1280, 720);
+ mw.show();
+ return a.exec();
+}
diff --git a/examples/opengl/stereoqopenglwidget/mainwindow.cpp b/examples/opengl/stereoqopenglwidget/mainwindow.cpp
new file mode 100644
index 0000000000..33f93ba7de
--- /dev/null
+++ b/examples/opengl/stereoqopenglwidget/mainwindow.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "mainwindow.h"
+#include <QApplication>
+#include <QMenuBar>
+#include "glwidget.h"
+
+MainWindow::MainWindow()
+{
+ GLWidget *glwidget = new GLWidget(qRgb(20, 20, 50));
+ setCentralWidget(glwidget);
+
+ QMenu *screenShotMenu = menuBar()->addMenu("&Screenshot");
+ screenShotMenu->addAction("Left buffer", this, [glwidget](){
+ glwidget->saveImage(QOpenGLWidget::LeftBuffer);
+ });
+
+ screenShotMenu->addAction("Right buffer", this, [glwidget](){
+ glwidget->saveImage(QOpenGLWidget::RightBuffer);
+ });
+
+ QMenu *helpMenu = menuBar()->addMenu("&Help");
+ helpMenu->addAction("About Qt", qApp, &QApplication::aboutQt);
+}
diff --git a/examples/opengl/stereoqopenglwidget/mainwindow.h b/examples/opengl/stereoqopenglwidget/mainwindow.h
new file mode 100644
index 0000000000..aa6f722e59
--- /dev/null
+++ b/examples/opengl/stereoqopenglwidget/mainwindow.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QTimer>
+#include <QGridLayout>
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLWidget)
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow();
+};
+
+#endif
diff --git a/examples/opengl/stereoqopenglwidget/stereoqopenglwidget.pro b/examples/opengl/stereoqopenglwidget/stereoqopenglwidget.pro
new file mode 100644
index 0000000000..197afff571
--- /dev/null
+++ b/examples/opengl/stereoqopenglwidget/stereoqopenglwidget.pro
@@ -0,0 +1,11 @@
+QT += widgets opengl openglwidgets
+
+SOURCES += main.cpp \
+ glwidget.cpp \
+ mainwindow.cpp
+
+HEADERS += glwidget.h \
+ mainwindow.h
+
+target.path = $$[QT_INSTALL_EXAMPLES]/opengl/stereoqopenglwidget
+INSTALLS += target
diff --git a/src/gui/painting/qbackingstoredefaultcompositor.cpp b/src/gui/painting/qbackingstoredefaultcompositor.cpp
index 076ae3e984..1dd116ac81 100644
--- a/src/gui/painting/qbackingstoredefaultcompositor.cpp
+++ b/src/gui/painting/qbackingstoredefaultcompositor.cpp
@@ -335,7 +335,7 @@ static QRhiGraphicsPipeline *createGraphicsPipeline(QRhi *rhi,
static const int UBUF_SIZE = 120;
-QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::createPerQuadData(QRhiTexture *texture)
+QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::createPerQuadData(QRhiTexture *texture, QRhiTexture *textureExtra)
{
PerQuadData d;
@@ -350,13 +350,24 @@ QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::crea
});
if (!d.srb->create())
qWarning("QBackingStoreDefaultCompositor: Failed to create srb");
-
d.lastUsedTexture = texture;
+ if (textureExtra) {
+ d.srbExtra = m_rhi->newShaderResourceBindings();
+ d.srbExtra->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUF_SIZE),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, m_sampler)
+ });
+ if (!d.srbExtra->create())
+ qWarning("QBackingStoreDefaultCompositor: Failed to create srb");
+ }
+
+ d.lastUsedTextureExtra = textureExtra;
+
return d;
}
-void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTexture *texture)
+void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *textureExtra)
{
// This whole check-if-texture-ptr-is-different is needed because there is
// nothing saying a QPlatformTextureList cannot return a different
@@ -371,8 +382,17 @@ void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTextu
});
d->srb->updateResources(QRhiShaderResourceBindings::BindingsAreSorted);
-
d->lastUsedTexture = texture;
+
+ if (textureExtra) {
+ d->srbExtra->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d->ubuf, 0, UBUF_SIZE),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, m_sampler)
+ });
+
+ d->srbExtra->updateResources(QRhiShaderResourceBindings::BindingsAreSorted);
+ d->lastUsedTextureExtra = textureExtra;
+ }
}
void QBackingStoreDefaultCompositor::updateUniforms(PerQuadData *d, QRhiResourceUpdateBatch *resourceUpdates,
@@ -534,11 +554,14 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
continue;
}
QRhiTexture *t = textures->texture(i);
+ QRhiTexture *tExtra = textures->textureExtra(i);
if (t) {
- if (!m_textureQuadData[i].isValid())
- m_textureQuadData[i] = createPerQuadData(t);
- else
- updatePerQuadData(&m_textureQuadData[i], t);
+ if (!m_textureQuadData[i].isValid()) {
+ m_textureQuadData[i] = createPerQuadData(t, tExtra);
+ }
+ else {
+ updatePerQuadData(&m_textureQuadData[i], t, tExtra);
+ }
updateUniforms(&m_textureQuadData[i], resourceUpdates, target, source, NoOption);
} else {
m_textureQuadData[i].reset();
@@ -549,47 +572,74 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
QRhiCommandBuffer *cb = swapchain->currentFrameCommandBuffer();
const QSize outputSizeInPixels = swapchain->currentPixelSize();
QColor clearColor = translucentBackground ? Qt::transparent : Qt::black;
- cb->beginPass(swapchain->currentFrameRenderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);
- cb->setGraphicsPipeline(m_psNoBlend);
- cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
- QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf, 0);
- cb->setVertexInput(0, 1, &vbufBinding);
+ cb->resourceUpdate(resourceUpdates);
- // Textures for renderToTexture widgets.
- for (int i = 0; i < textureWidgetCount; ++i) {
- if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
- if (m_textureQuadData[i].isValid()) {
- cb->setShaderResources(m_textureQuadData[i].srb);
- cb->draw(6);
+ auto render = [&](std::optional<QRhiSwapChain::StereoTargetBuffer> buffer = std::nullopt) {
+ QRhiRenderTarget* target = nullptr;
+ if (buffer.has_value())
+ target = swapchain->currentFrameRenderTarget(buffer.value());
+ else
+ target = swapchain->currentFrameRenderTarget();
+
+ cb->beginPass(target, clearColor, { 1.0f, 0 });
+
+ cb->setGraphicsPipeline(m_psNoBlend);
+ cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
+ QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf, 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+
+ // Textures for renderToTexture widgets.
+ for (int i = 0; i < textureWidgetCount; ++i) {
+ if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
+ if (m_textureQuadData[i].isValid()) {
+
+ QRhiShaderResourceBindings* srb = m_textureQuadData[i].srb;
+ if (buffer == QRhiSwapChain::RightBuffer && m_textureQuadData[i].srbExtra)
+ srb = m_textureQuadData[i].srbExtra;
+
+ cb->setShaderResources(srb);
+ cb->draw(6);
+ }
}
}
- }
- cb->setGraphicsPipeline(premultiplied ? m_psPremulBlend : m_psBlend);
+ cb->setGraphicsPipeline(premultiplied ? m_psPremulBlend : m_psBlend);
- // Backingstore texture with the normal widgets.
- if (m_texture) {
- cb->setShaderResources(m_widgetQuadData.srb);
- cb->draw(6);
- }
+ // Backingstore texture with the normal widgets.
+ if (m_texture) {
+ cb->setShaderResources(m_widgetQuadData.srb);
+ cb->draw(6);
+ }
- // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
- for (int i = 0; i < textureWidgetCount; ++i) {
- const QPlatformTextureList::Flags flags = textures->flags(i);
- if (flags.testFlag(QPlatformTextureList::StacksOnTop)) {
- if (m_textureQuadData[i].isValid()) {
- if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending))
- cb->setGraphicsPipeline(m_psPremulBlend);
- else
- cb->setGraphicsPipeline(m_psBlend);
- cb->setShaderResources(m_textureQuadData[i].srb);
- cb->draw(6);
+ // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
+ for (int i = 0; i < textureWidgetCount; ++i) {
+ const QPlatformTextureList::Flags flags = textures->flags(i);
+ if (flags.testFlag(QPlatformTextureList::StacksOnTop)) {
+ if (m_textureQuadData[i].isValid()) {
+ if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending))
+ cb->setGraphicsPipeline(m_psPremulBlend);
+ else
+ cb->setGraphicsPipeline(m_psBlend);
+
+ QRhiShaderResourceBindings* srb = m_textureQuadData[i].srb;
+ if (buffer == QRhiSwapChain::RightBuffer && m_textureQuadData[i].srbExtra)
+ srb = m_textureQuadData[i].srbExtra;
+
+ cb->setShaderResources(srb);
+ cb->draw(6);
+ }
}
}
- }
- cb->endPass();
+ cb->endPass();
+ };
+
+ if (swapchain->window()->format().stereo()) {
+ render(QRhiSwapChain::LeftBuffer);
+ render(QRhiSwapChain::RightBuffer);
+ } else
+ render();
rhi->endFrame(swapchain);
diff --git a/src/gui/painting/qbackingstoredefaultcompositor_p.h b/src/gui/painting/qbackingstoredefaultcompositor_p.h
index 75080f6994..d69c17f98f 100644
--- a/src/gui/painting/qbackingstoredefaultcompositor_p.h
+++ b/src/gui/painting/qbackingstoredefaultcompositor_p.h
@@ -70,21 +70,28 @@ private:
QRhiBuffer *ubuf = nullptr;
// All srbs are layout-compatible.
QRhiShaderResourceBindings *srb = nullptr;
+ QRhiShaderResourceBindings *srbExtra = nullptr; // may be null (used for stereo)
QRhiTexture *lastUsedTexture = nullptr;
+ QRhiTexture *lastUsedTextureExtra = nullptr; // may be null (used for stereo)
bool isValid() const { return ubuf && srb; }
void reset() {
delete ubuf;
ubuf = nullptr;
delete srb;
srb = nullptr;
+ if (srbExtra) {
+ delete srbExtra;
+ srbExtra = nullptr;
+ }
lastUsedTexture = nullptr;
+ lastUsedTextureExtra = nullptr;
}
};
PerQuadData m_widgetQuadData;
QVarLengthArray<PerQuadData, 8> m_textureQuadData;
- PerQuadData createPerQuadData(QRhiTexture *texture);
- void updatePerQuadData(PerQuadData *d, QRhiTexture *texture);
+ PerQuadData createPerQuadData(QRhiTexture *texture, QRhiTexture *textureExtra = nullptr);
+ void updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *textureExtra = nullptr);
void updateUniforms(PerQuadData *d, QRhiResourceUpdateBatch *resourceUpdates,
const QMatrix4x4 &target, const QMatrix3x3 &source, UpdateUniformOption option);
};
diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp
index 8a23003065..82e7778b86 100644
--- a/src/gui/painting/qplatformbackingstore.cpp
+++ b/src/gui/painting/qplatformbackingstore.cpp
@@ -37,6 +37,7 @@ struct QBackingstoreTextureInfo
{
void *source; // may be null
QRhiTexture *texture;
+ QRhiTexture *textureExtra;
QRect rect;
QRect clipRect;
QPlatformTextureList::Flags flags;
@@ -77,6 +78,12 @@ QRhiTexture *QPlatformTextureList::texture(int index) const
return d->textures.at(index).texture;
}
+QRhiTexture *QPlatformTextureList::textureExtra(int index) const
+{
+ Q_D(const QPlatformTextureList);
+ return d->textures.at(index).textureExtra;
+}
+
void *QPlatformTextureList::source(int index)
{
Q_D(const QPlatformTextureList);
@@ -123,6 +130,22 @@ void QPlatformTextureList::appendTexture(void *source, QRhiTexture *texture, con
QBackingstoreTextureInfo bi;
bi.source = source;
bi.texture = texture;
+ bi.textureExtra = nullptr;
+ bi.rect = geometry;
+ bi.clipRect = clipRect;
+ bi.flags = flags;
+ d->textures.append(bi);
+}
+
+void QPlatformTextureList::appendTexture(void *source, QRhiTexture *textureLeft, QRhiTexture *textureRight, const QRect &geometry,
+ const QRect &clipRect, Flags flags)
+{
+ Q_D(QPlatformTextureList);
+
+ QBackingstoreTextureInfo bi;
+ bi.source = source;
+ bi.texture = textureLeft;
+ bi.textureExtra = textureRight;
bi.rect = geometry;
bi.clipRect = clipRect;
bi.flags = flags;
diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h
index c6b66e57ef..40453574aa 100644
--- a/src/gui/painting/qplatformbackingstore.h
+++ b/src/gui/painting/qplatformbackingstore.h
@@ -103,6 +103,7 @@ public:
int count() const;
bool isEmpty() const { return count() == 0; }
QRhiTexture *texture(int index) const;
+ QRhiTexture *textureExtra(int index) const;
QRect geometry(int index) const;
QRect clipRect(int index) const;
void *source(int index);
@@ -112,6 +113,9 @@ public:
void appendTexture(void *source, QRhiTexture *texture, const QRect &geometry,
const QRect &clipRect = QRect(), Flags flags = { });
+
+ void appendTexture(void *source, QRhiTexture *textureLeft, QRhiTexture *textureRight, const QRect &geometry,
+ const QRect &clipRect = QRect(), Flags flags = { });
void clear();
Q_SIGNALS:
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 79c7c96158..ccb6db445b 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -4883,18 +4883,18 @@ QRhiResource::Type QRhiSwapChain::resourceType() const
is backed by two color buffers, one for each eye, instead of just one.
When stereoscopic rendering is not supported, the return value will be
- null. For the time being the only backend and 3D API where traditional
+ the default target. For the time being the only backend and 3D API where traditional
stereoscopic rendering is supported is OpenGL (excluding OpenGL ES), in
combination with \l QSurfaceFormat::StereoBuffers, assuming it is supported
by the graphics and display driver stack at run time. All other backends
- are going to return null from this overload.
+ are going to return the default render target from this overload.
\note the value must not be cached and reused between frames
*/
QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
{
Q_UNUSED(targetBuffer);
- return nullptr;
+ return currentFrameRenderTarget();
}
/*!
diff --git a/src/openglwidgets/qopenglwidget.cpp b/src/openglwidgets/qopenglwidget.cpp
index 1e3f32e3eb..7a0ac05cee 100644
--- a/src/openglwidgets/qopenglwidget.cpp
+++ b/src/openglwidgets/qopenglwidget.cpp
@@ -415,6 +415,26 @@ QT_BEGIN_NAMESPACE
certain desktop platforms (e.g. \macos) too. The stable,
cross-platform solution is always QOpenGLWidget.
+
+ \section1 Stereoscopic rendering
+
+ Starting from 6.5 QOpenGLWidget has support for stereoscopic rendering.
+ To enable it, set the QSurfaceFormat::StereoBuffers flag
+ globally before the window is created, using QSurfaceFormat::SetDefaultFormat().
+
+ \note Using setFormat() will not necessarily work because of how the flag is
+ handled internally.
+
+ This will trigger paintGL() to be called twice each frame,
+ once for each QOpenGLWidget::TargetBuffer. In paintGL(), call
+ currentTargetBuffer() to query which one is currently being drawn to.
+
+ \note For more control over the left and right color buffers, consider using
+ QOpenGLWindow + QWidget::createWindowContainer() instead.
+
+ \note This type of 3D rendering has certain hardware requirements,
+ like the graphics card needs to be setup with stereo support.
+
\e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other
countries.}
@@ -451,6 +471,20 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \enum QOpenGLWidget::TargetBuffer
+ \since 6.5
+
+ Specifies the buffer to use when stereoscopic rendering is enabled, which is
+ toggled by setting \l QSurfaceFormat::StereoBuffers.
+
+ \note LeftBuffer is always the default and used as fallback value when
+ stereoscopic rendering is disabled or not supported by the graphics driver.
+
+ \value LeftBuffer
+ \value RightBuffer
+ */
+
+/*!
\enum QOpenGLWidget::UpdateBehavior
\since 5.5
@@ -503,10 +537,10 @@ public:
void reset();
void resetRhiDependentResources();
- void recreateFbo();
+ void recreateFbos();
void ensureRhiDependentResources();
- QRhiTexture *texture() const override;
+ QWidgetPrivate::TextureData texture() const override;
QPlatformTextureList::Flags textureListFlags() override;
QPlatformBackingStoreRhiConfig rhiConfig() const override { return { QPlatformBackingStoreRhiConfig::OpenGL }; }
@@ -516,6 +550,10 @@ public:
void invalidateFbo();
+ void destroyFbos();
+
+ void setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
+ QImage grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer);
QImage grabFramebuffer() override;
void beginBackingStorePainting() override { inBackingStorePaint = true; }
void endBackingStorePainting() override { inBackingStorePaint = false; }
@@ -526,9 +564,9 @@ public:
void resolveSamples() override;
QOpenGLContext *context = nullptr;
- QRhiTexture *wrapperTexture = nullptr;
- QOpenGLFramebufferObject *fbo = nullptr;
- QOpenGLFramebufferObject *resolvedFbo = nullptr;
+ QRhiTexture *wrapperTextures[2] = {};
+ QOpenGLFramebufferObject *fbos[2] = {};
+ QOpenGLFramebufferObject *resolvedFbos[2] = {};
QOffscreenSurface *surface = nullptr;
QOpenGLPaintDevice *paintDevice = nullptr;
int requestedSamples = 0;
@@ -541,6 +579,7 @@ public:
bool hasBeenComposed = false;
bool flushPending = false;
bool inPaintGL = false;
+ QOpenGLWidget::TargetBuffer currentTargetBuffer = QOpenGLWidget::LeftBuffer;
};
void QOpenGLWidgetPaintDevicePrivate::beginPaint()
@@ -582,10 +621,11 @@ void QOpenGLWidgetPaintDevice::ensureActiveTarget()
if (QOpenGLContext::currentContext() != wd->context)
d->w->makeCurrent();
else
- wd->fbo->bind();
+ wd->fbos[wd->currentTargetBuffer]->bind();
+
if (!wd->inPaintGL)
- QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = wd->fbo->handle();
+ QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = wd->fbos[wd->currentTargetBuffer]->handle();
// When used as a viewport, drawing is done via opening a QPainter on the widget
// without going through paintEvent(). We will have to make sure a glFlush() is done
@@ -593,9 +633,9 @@ void QOpenGLWidgetPaintDevice::ensureActiveTarget()
wd->flushPending = true;
}
-QRhiTexture *QOpenGLWidgetPrivate::texture() const
+QWidgetPrivate::TextureData QOpenGLWidgetPrivate::texture() const
{
- return wrapperTexture;
+ return { wrapperTextures[QOpenGLWidget::LeftBuffer], wrapperTextures[QOpenGLWidget::RightBuffer] };
}
#ifndef GL_SRGB
@@ -637,10 +677,8 @@ void QOpenGLWidgetPrivate::reset()
delete paintDevice;
paintDevice = nullptr;
- delete fbo;
- fbo = nullptr;
- delete resolvedFbo;
- resolvedFbo = nullptr;
+
+ destroyFbos();
resetRhiDependentResources();
@@ -659,15 +697,22 @@ 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.
- delete wrapperTexture;
- wrapperTexture = nullptr;
+ delete wrapperTextures[0];
+ wrapperTextures[0] = nullptr;
+
+ if (q->format().stereo()) {
+ delete wrapperTextures[1];
+ wrapperTextures[1] = nullptr;
+ }
}
-void QOpenGLWidgetPrivate::recreateFbo()
+void QOpenGLWidgetPrivate::recreateFbos()
{
Q_Q(QOpenGLWidget);
@@ -675,10 +720,7 @@ void QOpenGLWidgetPrivate::recreateFbo()
context->makeCurrent(surface);
- delete fbo;
- fbo = nullptr;
- delete resolvedFbo;
- resolvedFbo = nullptr;
+ destroyFbos();
int samples = requestedSamples;
QOpenGLExtensions *extfuncs = static_cast<QOpenGLExtensions *>(context->functions());
@@ -692,21 +734,37 @@ void QOpenGLWidgetPrivate::recreateFbo()
format.setInternalTextureFormat(textureFormat);
const QSize deviceSize = q->size() * q->devicePixelRatio();
- fbo = new QOpenGLFramebufferObject(deviceSize, format);
+ fbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
if (samples > 0)
- resolvedFbo = new QOpenGLFramebufferObject(deviceSize);
+ resolvedFbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize);
+
+ const bool stereoEnabled = q->format().stereo();
+
+ if (stereoEnabled) {
+ fbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
+ if (samples > 0)
+ resolvedFbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize);
+ }
- textureFormat = fbo->format().internalTextureFormat();
+ textureFormat = fbos[QOpenGLWidget::LeftBuffer]->format().internalTextureFormat();
- fbo->bind();
+ currentTargetBuffer = QOpenGLWidget::LeftBuffer;
+ fbos[currentTargetBuffer]->bind();
context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ ensureRhiDependentResources();
+
+ if (stereoEnabled) {
+ currentTargetBuffer = QOpenGLWidget::RightBuffer;
+ fbos[currentTargetBuffer]->bind();
+ context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ ensureRhiDependentResources();
+ }
+
flushPending = true; // Make sure the FBO is initialized before use
paintDevice->setSize(deviceSize);
paintDevice->setDevicePixelRatio(q->devicePixelRatio());
- ensureRhiDependentResources();
-
emit q->resized();
}
@@ -721,13 +779,15 @@ void QOpenGLWidgetPrivate::ensureRhiDependentResources()
// If there is no rhi, because we are completely offscreen, then there's no wrapperTexture either
if (rhi && rhi->backend() == QRhi::OpenGLES2) {
const QSize deviceSize = q->size() * q->devicePixelRatio();
- if (!wrapperTexture || wrapperTexture->pixelSize() != deviceSize) {
- const uint textureId = resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0);
- if (!wrapperTexture)
- wrapperTexture = rhi->newTexture(QRhiTexture::RGBA8, deviceSize, 1, QRhiTexture::RenderTarget);
+ if (!wrapperTextures[currentTargetBuffer] || wrapperTextures[currentTargetBuffer]->pixelSize() != deviceSize) {
+ const uint textureId = resolvedFbos[currentTargetBuffer] ?
+ resolvedFbos[currentTargetBuffer]->texture()
+ : (fbos[currentTargetBuffer] ? fbos[currentTargetBuffer]->texture() : 0);
+ if (!wrapperTextures[currentTargetBuffer])
+ wrapperTextures[currentTargetBuffer] = rhi->newTexture(QRhiTexture::RGBA8, deviceSize, 1, QRhiTexture::RenderTarget);
else
- wrapperTexture->setPixelSize(deviceSize);
- if (!wrapperTexture->createFrom({textureId, 0 }))
+ wrapperTextures[currentTargetBuffer]->setPixelSize(deviceSize);
+ if (!wrapperTextures[currentTargetBuffer]->createFrom({textureId, 0 }))
qWarning("QOpenGLWidget: Failed to create wrapper texture");
}
}
@@ -836,10 +896,10 @@ void QOpenGLWidgetPrivate::initialize()
void QOpenGLWidgetPrivate::resolveSamples()
{
Q_Q(QOpenGLWidget);
- if (resolvedFbo) {
+ if (resolvedFbos[currentTargetBuffer]) {
q->makeCurrent();
- QRect rect(QPoint(0, 0), fbo->size());
- QOpenGLFramebufferObject::blitFramebuffer(resolvedFbo, rect, fbo, rect);
+ QRect rect(QPoint(0, 0), fbos[currentTargetBuffer]->size());
+ QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[currentTargetBuffer], rect, fbos[currentTargetBuffer], rect);
flushPending = true;
}
}
@@ -851,33 +911,56 @@ void QOpenGLWidgetPrivate::render()
if (fakeHidden || !initialized)
return;
- q->makeCurrent();
+ setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
QOpenGLContext *ctx = QOpenGLContext::currentContext();
if (!ctx) {
qWarning("QOpenGLWidget: No current context, cannot render");
return;
}
- if (!fbo) {
+
+ if (!fbos[QOpenGLWidget::LeftBuffer]) {
qWarning("QOpenGLWidget: No fbo, cannot render");
return;
}
+ const bool stereoEnabled = q->format().stereo();
+ if (stereoEnabled) {
+ static bool warningGiven = false;
+ if (!fbos[QOpenGLWidget::RightBuffer] && !warningGiven) {
+ qWarning("QOpenGLWidget: Stereo is enabled, but no right buffer. Using only left buffer");
+ warningGiven = true;
+ }
+ }
+
if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
invalidateFbo();
+
+ if (stereoEnabled && fbos[QOpenGLWidget::RightBuffer]) {
+ setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
+ invalidateFbo();
+ setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
+ }
+
hasBeenComposed = false;
}
QOpenGLFunctions *f = ctx->functions();
- QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbo->handle();
-
f->glViewport(0, 0, q->width() * q->devicePixelRatio(), q->height() * q->devicePixelRatio());
inPaintGL = true;
+
+ QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
q->paintGL();
- inPaintGL = false;
- flushPending = true;
+ if (stereoEnabled && fbos[QOpenGLWidget::RightBuffer]) {
+ setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
+ QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
+ q->paintGL();
+ }
QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
+
+ inPaintGL = false;
+ flushPending = true;
}
void QOpenGLWidgetPrivate::invalidateFbo()
@@ -906,25 +989,47 @@ void QOpenGLWidgetPrivate::invalidateFbo()
}
}
+void QOpenGLWidgetPrivate::destroyFbos()
+{
+ delete fbos[QOpenGLWidget::LeftBuffer];
+ fbos[QOpenGLWidget::LeftBuffer] = nullptr;
+ delete resolvedFbos[QOpenGLWidget::LeftBuffer];
+ resolvedFbos[QOpenGLWidget::LeftBuffer] = nullptr;
+
+ delete fbos[QOpenGLWidget::RightBuffer];
+ fbos[QOpenGLWidget::RightBuffer] = nullptr;
+ delete resolvedFbos[QOpenGLWidget::RightBuffer];
+ resolvedFbos[QOpenGLWidget::RightBuffer] = nullptr;
+}
+
QImage QOpenGLWidgetPrivate::grabFramebuffer()
{
+ return grabFramebuffer(QOpenGLWidget::LeftBuffer);
+}
+
+QImage QOpenGLWidgetPrivate::grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer)
+{
Q_Q(QOpenGLWidget);
initialize();
if (!initialized)
return QImage();
- if (!fbo) // could be completely offscreen, without ever getting a resize event
- recreateFbo();
+ // 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())
+ targetBuffer = QOpenGLWidget::LeftBuffer;
+
+ if (!fbos[targetBuffer]) // could be completely offscreen, without ever getting a resize event
+ recreateFbos();
if (!inPaintGL)
render();
- if (resolvedFbo) {
+ setCurrentTargetBuffer(targetBuffer);
+ if (resolvedFbos[targetBuffer]) {
resolveSamples();
- resolvedFbo->bind();
- } else {
- q->makeCurrent();
+ resolvedFbos[targetBuffer]->bind();
}
const bool hasAlpha = q->format().hasAlpha();
@@ -934,8 +1039,9 @@ QImage QOpenGLWidgetPrivate::grabFramebuffer()
// While we give no guarantees of what is going to be left bound, prefer the
// multisample fbo instead of the resolved one. Clients may continue to
// render straight after calling this function.
- if (resolvedFbo)
- q->makeCurrent();
+ if (resolvedFbos[targetBuffer]) {
+ setCurrentTargetBuffer(targetBuffer);
+ }
return res;
}
@@ -954,8 +1060,8 @@ void QOpenGLWidgetPrivate::resizeViewportFramebuffer()
if (!initialized)
return;
- if (!fbo || q->size() * q->devicePixelRatio() != fbo->size()) {
- recreateFbo();
+ if (!fbos[currentTargetBuffer] || q->size() * q->devicePixelRatio() != fbos[currentTargetBuffer]->size()) {
+ recreateFbos();
q->update();
}
}
@@ -1143,8 +1249,8 @@ void QOpenGLWidget::makeCurrent()
d->context->makeCurrent(d->surface);
- if (d->fbo) // there may not be one if we are in reset()
- d->fbo->bind();
+ if (d->fbos[d->currentTargetBuffer]) // there may not be one if we are in reset()
+ d->fbos[d->currentTargetBuffer]->bind();
}
/*!
@@ -1192,7 +1298,31 @@ QOpenGLContext *QOpenGLWidget::context() const
GLuint QOpenGLWidget::defaultFramebufferObject() const
{
Q_D(const QOpenGLWidget);
- return d->fbo ? d->fbo->handle() : 0;
+ return d->fbos[TargetBuffer::LeftBuffer] ? d->fbos[TargetBuffer::LeftBuffer]->handle() : 0;
+}
+
+/*!
+ \return The framebuffer object handle of the specified target buffer or
+ \c 0 if not yet initialized.
+
+ \note Calling this overload only makes sense if \l QSurfaceFormat::StereoBuffer is enabled
+ and supported by the hardware. Will return the default buffer if it's not.
+
+ \note The framebuffer object belongs to the context returned by context()
+ and may not be accessible from other contexts.
+
+ \note The context and the framebuffer object used by the widget changes when
+ reparenting the widget via setParent(). In addition, the framebuffer object
+ changes on each resize.
+
+ \since 6.5
+
+ \sa context()
+ */
+GLuint QOpenGLWidget::defaultFramebufferObject(TargetBuffer targetBuffer) const
+{
+ Q_D(const QOpenGLWidget);
+ return d->fbos[targetBuffer] ? d->fbos[targetBuffer]->handle() : 0;
}
/*!
@@ -1241,7 +1371,15 @@ void QOpenGLWidget::resizeGL(int w, int h)
other state is set and no clearing or drawing is performed by the
framework.
- \sa initializeGL(), resizeGL()
+ When \l QSurfaceFormat::StereoBuffers is enabled, this function
+ will be called twice - once for each buffer. Query what buffer is
+ currently bound by calling currentTargetBuffer().
+
+ \note The framebuffer of each target will be drawn to even when
+ stereoscopic rendering is not supported by the hardware.
+ Only the left buffer will actually be visible in the window.
+
+ \sa initializeGL(), resizeGL(), currentTargetBuffer()
*/
void QOpenGLWidget::paintGL()
{
@@ -1270,7 +1408,7 @@ void QOpenGLWidget::resizeEvent(QResizeEvent *e)
if (!d->initialized)
return;
- d->recreateFbo();
+ d->recreateFbos();
// Make sure our own context is current before invoking user overrides. If
// the fbo was recreated then there's a chance something else is current now.
makeCurrent();
@@ -1315,6 +1453,39 @@ QImage QOpenGLWidget::grabFramebuffer()
}
/*!
+ Renders and returns a 32-bit RGB image of the framebuffer of the specified target buffer.
+ This overload only makes sense to call when \l QSurfaceFormat::StereoBuffers is enabled.
+ Grabbing the framebuffer of the right target buffer will return the default image
+ if stereoscopic rendering is disabled or if not supported by the hardware.
+
+ \note This is a potentially expensive operation because it relies on glReadPixels()
+ to read back the pixels. This may be slow and can stall the GPU pipeline.
+
+ \since 6.5
+*/
+QImage QOpenGLWidget::grabFramebuffer(TargetBuffer targetBuffer)
+{
+ Q_D(QOpenGLWidget);
+ return d->grabFramebuffer(targetBuffer);
+}
+
+/*!
+ Returns the currently active target buffer. This will be the left buffer by default,
+ the right buffer is only used when \l QSurfaceFormat::StereoBuffers is enabled.
+ When stereoscopic rendering is enabled, this can be queried in paintGL() to know
+ what buffer is currently in use. paintGL() will be called twice, once for each target.
+
+ \since 6.5
+
+ \sa paintGL()
+*/
+QOpenGLWidget::TargetBuffer QOpenGLWidget::currentTargetBuffer() const
+{
+ Q_D(const QOpenGLWidget);
+ return d->currentTargetBuffer;
+}
+
+/*!
\reimp
*/
int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
@@ -1414,6 +1585,13 @@ QPaintEngine *QOpenGLWidget::paintEngine() const
return d->paintDevice->paintEngine();
}
+void QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
+{
+ Q_Q(QOpenGLWidget);
+ currentTargetBuffer = targetBuffer;
+ q->makeCurrent();
+}
+
/*!
\reimp
*/
@@ -1433,7 +1611,7 @@ bool QOpenGLWidget::event(QEvent *e)
break;
Q_FALLTHROUGH();
case QEvent::Show: // reparenting may not lead to a resize so reinitialize on Show too
- if (d->initialized && !d->wrapperTexture && window()->windowHandle()) {
+ if (d->initialized && !d->wrapperTextures[d->currentTargetBuffer] && window()->windowHandle()) {
// Special case: did grabFramebuffer() for a hidden widget that then became visible.
// Recreate all resources since the context now needs to share with the TLW's.
if (!QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
@@ -1443,7 +1621,7 @@ bool QOpenGLWidget::event(QEvent *e)
if (!d->initialized && !size().isEmpty() && repaintManager->rhi()) {
d->initialize();
if (d->initialized) {
- d->recreateFbo();
+ d->recreateFbos();
// QTBUG-89812: generate a paint event, like resize would do,
// otherwise a QOpenGLWidget in a QDockWidget may not show the
// content upon (un)docking.
@@ -1454,7 +1632,7 @@ bool QOpenGLWidget::event(QEvent *e)
break;
case QEvent::ScreenChangeInternal:
if (d->initialized && d->paintDevice->devicePixelRatio() != devicePixelRatio())
- d->recreateFbo();
+ d->recreateFbos();
break;
default:
break;
diff --git a/src/openglwidgets/qopenglwidget.h b/src/openglwidgets/qopenglwidget.h
index edd731ae8e..84097854e5 100644
--- a/src/openglwidgets/qopenglwidget.h
+++ b/src/openglwidgets/qopenglwidget.h
@@ -25,6 +25,11 @@ public:
PartialUpdate
};
+ enum TargetBuffer {
+ LeftBuffer = 0, // Default
+ RightBuffer // Only used when QSurfaceFormat::StereoBuffers is enabled
+ };
+
explicit QOpenGLWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~QOpenGLWidget();
@@ -44,8 +49,12 @@ public:
QOpenGLContext *context() const;
GLuint defaultFramebufferObject() const;
+ GLuint defaultFramebufferObject(TargetBuffer targetBuffer) const;
QImage grabFramebuffer();
+ QImage grabFramebuffer(TargetBuffer targetBuffer);
+
+ TargetBuffer currentTargetBuffer() const;
Q_SIGNALS:
void aboutToCompose();
diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
index 65e368d945..ea03e9c708 100644
--- a/src/widgets/kernel/qwidget_p.h
+++ b/src/widgets/kernel/qwidget_p.h
@@ -579,7 +579,13 @@ public:
virtual QPlatformBackingStoreRhiConfig rhiConfig() const { return {}; }
- virtual QRhiTexture *texture() const { return nullptr; }
+ // Note that textureRight may be null, as it's only used in stereoscopic rendering
+ struct TextureData {
+ QRhiTexture *textureLeft = nullptr;
+ QRhiTexture *textureRight = nullptr;
+ };
+
+ virtual TextureData texture() const { return {}; }
virtual QPlatformTextureList::Flags textureListFlags() {
Q_Q(QWidget);
return q->testAttribute(Qt::WA_AlwaysStackOnTop)
diff --git a/src/widgets/kernel/qwidgetrepaintmanager.cpp b/src/widgets/kernel/qwidgetrepaintmanager.cpp
index cdd8cee016..16db49f95e 100644
--- a/src/widgets/kernel/qwidgetrepaintmanager.cpp
+++ b/src/widgets/kernel/qwidgetrepaintmanager.cpp
@@ -539,7 +539,8 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget,
if (wd->renderToTexture) {
QPlatformTextureList::Flags flags = wd->textureListFlags();
const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
- widgetTextures->appendTexture(widget, wd->texture(), rect, wd->clipRect(), flags);
+ QWidgetPrivate::TextureData data = wd->texture();
+ widgetTextures->appendTexture(widget, data.textureLeft, data.textureRight, rect, wd->clipRect(), flags);
}
for (int i = 0; i < wd->children.size(); ++i) {