summaryrefslogtreecommitdiffstats
path: root/src
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 /src
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>
Diffstat (limited to 'src')
-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
9 files changed, 383 insertions, 105 deletions
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) {