From ee2dbcada81f5220a05414d7bf9d5eeebdda8972 Mon Sep 17 00:00:00 2001 From: Kristoffer Skau Date: Thu, 27 Oct 2022 15:38:53 +0200 Subject: [PATCH] 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 --- examples/opengl/CMakeLists.txt | 1 + .../doc/images/stereoexample-leftbuffer.png | Bin 0 -> 9987 bytes .../doc/images/stereoexample-rightbuffer.png | Bin 0 -> 10002 bytes .../opengl/doc/src/stereoqopenglwidget.qdoc | 41 +++ examples/opengl/opengl.pro | 3 +- .../opengl/stereoqopenglwidget/CMakeLists.txt | 41 +++ .../opengl/stereoqopenglwidget/glwidget.cpp | 277 ++++++++++++++++ .../opengl/stereoqopenglwidget/glwidget.h | 53 ++++ examples/opengl/stereoqopenglwidget/main.cpp | 31 ++ .../opengl/stereoqopenglwidget/mainwindow.cpp | 25 ++ .../opengl/stereoqopenglwidget/mainwindow.h | 21 ++ .../stereoqopenglwidget.pro | 11 + .../qbackingstoredefaultcompositor.cpp | 128 +++++--- .../qbackingstoredefaultcompositor_p.h | 11 +- src/gui/painting/qplatformbackingstore.cpp | 23 ++ src/gui/painting/qplatformbackingstore.h | 4 + src/gui/rhi/qrhi.cpp | 6 +- src/openglwidgets/qopenglwidget.cpp | 296 ++++++++++++++---- src/openglwidgets/qopenglwidget.h | 9 + src/widgets/kernel/qwidget_p.h | 8 +- src/widgets/kernel/qwidgetrepaintmanager.cpp | 3 +- 21 files changed, 886 insertions(+), 106 deletions(-) create mode 100644 examples/opengl/doc/images/stereoexample-leftbuffer.png create mode 100644 examples/opengl/doc/images/stereoexample-rightbuffer.png create mode 100644 examples/opengl/doc/src/stereoqopenglwidget.qdoc create mode 100644 examples/opengl/stereoqopenglwidget/CMakeLists.txt create mode 100644 examples/opengl/stereoqopenglwidget/glwidget.cpp create mode 100644 examples/opengl/stereoqopenglwidget/glwidget.h create mode 100644 examples/opengl/stereoqopenglwidget/main.cpp create mode 100644 examples/opengl/stereoqopenglwidget/mainwindow.cpp create mode 100644 examples/opengl/stereoqopenglwidget/mainwindow.h create mode 100644 examples/opengl/stereoqopenglwidget/stereoqopenglwidget.pro diff --git a/examples/opengl/CMakeLists.txt b/examples/opengl/CMakeLists.txt index 9c6768f3248..d2c7ece2fd6 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 0000000000000000000000000000000000000000..6ae7d5118bb316c26c3ed0821f752cc0cd473c81 GIT binary patch literal 9987 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 literal 0 HcmV?d00001 diff --git a/examples/opengl/doc/images/stereoexample-rightbuffer.png b/examples/opengl/doc/images/stereoexample-rightbuffer.png new file mode 100644 index 0000000000000000000000000000000000000000..3e2dc3c6714a1f7c4116c58b610194b0edbfd30e GIT binary patch literal 10002 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 +#include +#include +#include +#include +#include +#include +#include + +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 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 00000000000..0014ee37c1b --- /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 +#include +#include +#include +#include +#include +#include +#include + + +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 m_vertices; + QList 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 00000000000..8aad756ecab --- /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 +#include +#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 00000000000..33f93ba7de8 --- /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 +#include +#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 00000000000..aa6f722e590 --- /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 +#include +#include + +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 00000000000..197afff571e --- /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 076ae3e9848..1dd116ac814 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 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 75080f69946..d69c17f98f3 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 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 8a230030656..82e7778b86a 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 c6b66e57efd..40453574aaf 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 79c7c96158a..ccb6db445be 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 1e3f32e3eb6..7a0ac05cee2 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.} @@ -450,6 +470,20 @@ QT_BEGIN_NAMESPACE due to resizing the widget. */ +/*! + \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(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); - textureFormat = fbo->format().internalTextureFormat(); + const bool stereoEnabled = q->format().stereo(); - fbo->bind(); + if (stereoEnabled) { + fbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize, format); + if (samples > 0) + resolvedFbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize); + } + + textureFormat = fbos[QOpenGLWidget::LeftBuffer]->format().internalTextureFormat(); + + 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(); + + 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; - - QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0; } void QOpenGLWidgetPrivate::invalidateFbo() @@ -906,7 +989,25 @@ 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); @@ -914,17 +1015,21 @@ QImage QOpenGLWidgetPrivate::grabFramebuffer() 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(); @@ -1314,6 +1452,39 @@ QImage QOpenGLWidget::grabFramebuffer() return d->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 */ @@ -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 edd731ae8ec..84097854e59 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 65e368d9458..ea03e9c7084 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 cdd8cee0168..16db49f95e3 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) {