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>
This commit is contained in:
Kristoffer Skau 2022-10-27 15:38:53 +02:00
parent 8155bd5426
commit ee2dbcada8
21 changed files with 886 additions and 106 deletions

View File

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -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
*/

View File

@ -14,5 +14,6 @@ qtHaveModule(widgets) {
cube \
textures \
hellogles3 \
computegles31
computegles31 \
stereoqopenglwidget
}

View File

@ -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}"
)

View File

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

View File

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

View File

@ -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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {