diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gui/painting/qbackingstoredefaultcompositor.cpp | 128 | ||||
-rw-r--r-- | src/gui/painting/qbackingstoredefaultcompositor_p.h | 11 | ||||
-rw-r--r-- | src/gui/painting/qplatformbackingstore.cpp | 23 | ||||
-rw-r--r-- | src/gui/painting/qplatformbackingstore.h | 4 | ||||
-rw-r--r-- | src/gui/rhi/qrhi.cpp | 6 | ||||
-rw-r--r-- | src/openglwidgets/qopenglwidget.cpp | 296 | ||||
-rw-r--r-- | src/openglwidgets/qopenglwidget.h | 9 | ||||
-rw-r--r-- | src/widgets/kernel/qwidget_p.h | 8 | ||||
-rw-r--r-- | src/widgets/kernel/qwidgetrepaintmanager.cpp | 3 |
9 files changed, 383 insertions, 105 deletions
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<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 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<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 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.} @@ -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 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) { |