summaryrefslogtreecommitdiffstats
path: root/src/openglwidgets/qopenglwidget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/openglwidgets/qopenglwidget.cpp')
-rw-r--r--src/openglwidgets/qopenglwidget.cpp510
1 files changed, 398 insertions, 112 deletions
diff --git a/src/openglwidgets/qopenglwidget.cpp b/src/openglwidgets/qopenglwidget.cpp
index d56a5e16cd..4a0bf7f492 100644
--- a/src/openglwidgets/qopenglwidget.cpp
+++ b/src/openglwidgets/qopenglwidget.cpp
@@ -23,8 +23,7 @@
#include <QtWidgets/private/qwidget_p.h>
#include <QtWidgets/private/qwidgetrepaintmanager_p.h>
-#include <QtGui/private/qrhi_p.h>
-#include <QtGui/private/qrhigles2_p.h>
+#include <rhi/qrhi.h>
QT_BEGIN_NAMESPACE
@@ -57,7 +56,7 @@ QT_BEGIN_NAMESPACE
\endlist
If you need to trigger a repaint from places other than paintGL() (a
- typical example is when using \l{QTimer}{timers} to animate scenes),
+ typical example is when using \l{QChronoTimer}{timers} to animate scenes),
you should call the widget's update() function to schedule an update.
Your widget's OpenGL rendering context is made current when
@@ -145,7 +144,7 @@ QT_BEGIN_NAMESPACE
loading which means applications are not directly linking to an GL
implementation and thus direct function calls are not feasible.
- In paintGL() the current context is always accessible by caling
+ In paintGL() the current context is always accessible by calling
QOpenGLContext::currentContext(). From this context an already initialized,
ready-to-be-used QOpenGLFunctions instance is retrievable by calling
QOpenGLContext::functions(). An alternative to prefixing every GL call is to
@@ -164,7 +163,7 @@ QT_BEGIN_NAMESPACE
\section1 Code Examples
- To get started, the simplest QOpenGLWidget subclass could like like the following:
+ To get started, the simplest QOpenGLWidget subclass could look like the following:
\snippet code/doc_gui_widgets_qopenglwidget.cpp 0
@@ -178,6 +177,13 @@ QT_BEGIN_NAMESPACE
\snippet code/doc_gui_widgets_qopenglwidget.cpp 2
+ \note It is up to the application to ensure depth and stencil buffers are
+ requested from the underlying windowing system interface. Without requesting
+ a non-zero depth buffer size there is no guarantee that a depth buffer will
+ be available, and as a result depth testing related OpenGL operations may
+ fail to function as expected. Commonly used depth and stencil buffer size
+ requests are 24 and 8, respectively.
+
With OpenGL 3.0+ contexts, when portability is not important, the versioned
QOpenGLFunctions variants give easy access to all the modern OpenGL functions
available in a given version:
@@ -415,6 +421,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 +477,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
@@ -464,9 +504,8 @@ QT_BEGIN_NAMESPACE
benefits on certain hardware architectures common in the mobile and
embedded space when a framebuffer object is used as the rendering target.
The framebuffer object is invalidated between frames with
- glDiscardFramebufferEXT if supported or a glClear. Please see the
- documentation of EXT_discard_framebuffer for more information:
- https://www.khronos.org/registry/gles/extensions/EXT/EXT_discard_framebuffer.txt
+ glInvalidateFramebuffer (if supported), or, as fallbacks,
+ glDiscardFramebufferEXT (if supported) or a call to glClear.
\value PartialUpdate The framebuffer objects color buffer and ancillary
buffers are not invalidated between frames.
@@ -503,10 +542,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 }; }
@@ -514,21 +553,35 @@ public:
void initialize();
void render();
- void invalidateFbo();
+ static constexpr GLenum gl_color_attachment0 = 0x8CE0; // GL_COLOR_ATTACHMENT0
+ static constexpr GLenum gl_depth_attachment = 0x8D00; // GL_DEPTH_ATTACHMENT
+ static constexpr GLenum gl_stencil_attachment = 0x8D20; // GL_STENCIL_ATTACHMENT
+ static constexpr GLenum gl_depth_stencil_attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT
+
+ void invalidateFboBeforePainting();
+ void invalidateFboAfterPainting();
+
+ void destroyFbos();
+ bool setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
+ QImage grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer);
QImage grabFramebuffer() override;
void beginBackingStorePainting() override { inBackingStorePaint = true; }
void endBackingStorePainting() override { inBackingStorePaint = false; }
void beginCompose() override;
void endCompose() override;
void initializeViewportFramebuffer() override;
+ bool isStereoEnabled() override;
+ bool toggleStereoTargetBuffer() override;
void resizeViewportFramebuffer() override;
void resolveSamples() override;
+ void resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
+
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 +594,7 @@ public:
bool hasBeenComposed = false;
bool flushPending = false;
bool inPaintGL = false;
+ QOpenGLWidget::TargetBuffer currentTargetBuffer = QOpenGLWidget::LeftBuffer;
};
void QOpenGLWidgetPaintDevicePrivate::beginPaint()
@@ -582,10 +636,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 +648,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,12 +692,8 @@ void QOpenGLWidgetPrivate::reset()
delete paintDevice;
paintDevice = nullptr;
- delete fbo;
- fbo = nullptr;
- delete resolvedFbo;
- resolvedFbo = nullptr;
- resetRhiDependentResources();
+ destroyFbos();
if (initialized)
q->doneCurrent();
@@ -663,11 +714,16 @@ void QOpenGLWidgetPrivate::resetRhiDependentResources()
// 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 (isStereoEnabled()) {
+ delete wrapperTextures[1];
+ wrapperTextures[1] = nullptr;
+ }
}
-void QOpenGLWidgetPrivate::recreateFbo()
+void QOpenGLWidgetPrivate::recreateFbos()
{
Q_Q(QOpenGLWidget);
@@ -675,10 +731,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 +745,38 @@ 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 stereo = isStereoEnabled();
- fbo->bind();
+ if (stereo) {
+ 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 (stereo) {
+ currentTargetBuffer = QOpenGLWidget::RightBuffer;
+ fbos[currentTargetBuffer]->bind();
+ context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ ensureRhiDependentResources();
+ currentTargetBuffer = QOpenGLWidget::LeftBuffer;
+ }
+
flushPending = true; // Make sure the FBO is initialized before use
paintDevice->setSize(deviceSize);
paintDevice->setDevicePixelRatio(q->devicePixelRatio());
- ensureRhiDependentResources();
-
emit q->resized();
}
@@ -714,20 +784,20 @@ void QOpenGLWidgetPrivate::ensureRhiDependentResources()
{
Q_Q(QOpenGLWidget);
- QRhi *rhi = nullptr;
- if (QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(q->window())->maybeRepaintManager())
- rhi = repaintManager->rhi();
+ QRhi *rhi = QWidgetPrivate::rhi();
// If there is no rhi, because we are completely offscreen, then there's no wrapperTexture either
- if (rhi) {
+ 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");
}
}
@@ -760,7 +830,6 @@ void QOpenGLWidgetPrivate::initialize()
// If no global shared context get our toplevel's context with which we
// will share in order to make the texture usable by the underlying window's backingstore.
QWidget *tlw = q->window();
- QWidgetPrivate *tlwd = get(tlw);
// Do not include the sample count. Requesting a multisampled context is not necessary
// since we render into an FBO, never to an actual surface. What's more, attempting to
@@ -769,9 +838,7 @@ void QOpenGLWidgetPrivate::initialize()
requestedSamples = requestedFormat.samples();
requestedFormat.setSamples(0);
- QRhi *rhi = nullptr;
- if (QWidgetRepaintManager *repaintManager = tlwd->maybeRepaintManager())
- rhi = repaintManager->rhi();
+ QRhi *rhi = QWidgetPrivate::rhi();
// Could be that something else already initialized the window with some
// other graphics API for the QRhi, that's not good.
@@ -788,9 +855,11 @@ void QOpenGLWidgetPrivate::initialize()
context = new QOpenGLContext;
context->setFormat(requestedFormat);
- if (contextFromRhi) {
- context->setShareContext(contextFromRhi);
- context->setScreen(contextFromRhi->screen());
+
+ QOpenGLContext *shareContext = contextFromRhi ? contextFromRhi : qt_gl_global_share_context();
+ if (shareContext) {
+ context->setShareContext(shareContext);
+ context->setScreen(shareContext->screen());
}
if (Q_UNLIKELY(!context->create())) {
qWarning("QOpenGLWidget: Failed to create context");
@@ -835,11 +904,17 @@ void QOpenGLWidgetPrivate::initialize()
void QOpenGLWidgetPrivate::resolveSamples()
{
+ resolveSamplesForBuffer(QOpenGLWidget::LeftBuffer);
+ resolveSamplesForBuffer(QOpenGLWidget::RightBuffer);
+}
+
+void QOpenGLWidgetPrivate::resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
+{
Q_Q(QOpenGLWidget);
- if (resolvedFbo) {
- q->makeCurrent();
- QRect rect(QPoint(0, 0), fbo->size());
- QOpenGLFramebufferObject::blitFramebuffer(resolvedFbo, rect, fbo, rect);
+ if (resolvedFbos[targetBuffer]) {
+ q->makeCurrent(targetBuffer);
+ QRect rect(QPoint(0, 0), fbos[targetBuffer]->size());
+ QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[targetBuffer], rect, fbos[targetBuffer], rect);
flushPending = true;
}
}
@@ -851,73 +926,151 @@ void QOpenGLWidgetPrivate::render()
if (fakeHidden || !initialized)
return;
- q->makeCurrent();
+ setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
QOpenGLContext *ctx = QOpenGLContext::currentContext();
- Q_ASSERT(ctx && fbo);
+ if (!ctx) {
+ qWarning("QOpenGLWidget: No current context, cannot render");
+ return;
+ }
+
+ if (!fbos[QOpenGLWidget::LeftBuffer]) {
+ qWarning("QOpenGLWidget: No fbo, cannot render");
+ return;
+ }
+
+ const bool stereo = isStereoEnabled();
+ if (stereo) {
+ 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();
+ invalidateFboBeforePainting();
+
+ if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
+ setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
+ invalidateFboBeforePainting();
+ 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;
+
+#ifdef Q_OS_WASM
+ f->glDepthMask(GL_TRUE);
+#endif
+
+ QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
+
+ f->glUseProgram(0);
+ f->glBindBuffer(GL_ARRAY_BUFFER, 0);
+ f->glEnable(GL_BLEND);
+
q->paintGL();
+ if (updateBehavior == QOpenGLWidget::NoPartialUpdate)
+ invalidateFboAfterPainting();
+
+ if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
+ setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
+ QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
+ q->paintGL();
+ if (updateBehavior == QOpenGLWidget::NoPartialUpdate)
+ invalidateFboAfterPainting();
+ }
+ QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
+
inPaintGL = false;
flushPending = true;
-
- QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
}
-void QOpenGLWidgetPrivate::invalidateFbo()
+void QOpenGLWidgetPrivate::invalidateFboBeforePainting()
{
QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
if (f->hasOpenGLExtension(QOpenGLExtensions::DiscardFramebuffer)) {
- const int gl_color_attachment0 = 0x8CE0; // GL_COLOR_ATTACHMENT0
- const int gl_depth_attachment = 0x8D00; // GL_DEPTH_ATTACHMENT
- const int gl_stencil_attachment = 0x8D20; // GL_STENCIL_ATTACHMENT
-#ifdef Q_OS_WASM
- // webgl does not allow separate depth and stencil attachments
- // QTBUG-69913
- const int gl_depth_stencil_attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT
-
const GLenum attachments[] = {
- gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment, gl_depth_stencil_attachment
- };
-#else
- const GLenum attachments[] = {
- gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment
- };
+ gl_color_attachment0,
+ gl_depth_attachment,
+ gl_stencil_attachment,
+#ifdef Q_OS_WASM
+ // webgl does not allow separate depth and stencil attachments
+ // QTBUG-69913
+ gl_depth_stencil_attachment
#endif
- f->glDiscardFramebufferEXT(GL_FRAMEBUFFER, sizeof attachments / sizeof *attachments, attachments);
+ };
+ f->discardFramebuffer(GL_FRAMEBUFFER, GLsizei(std::size(attachments)), attachments);
} else {
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
}
+void QOpenGLWidgetPrivate::invalidateFboAfterPainting()
+{
+ QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
+ if (f->hasOpenGLExtension(QOpenGLExtensions::DiscardFramebuffer)) {
+ const GLenum attachments[] = {
+ gl_depth_attachment,
+ gl_stencil_attachment,
+#ifdef Q_OS_WASM
+ // webgl does not allow separate depth and stencil attachments
+ // QTBUG-69913
+ gl_depth_stencil_attachment
+#endif
+ };
+ f->discardFramebuffer(GL_FRAMEBUFFER, GLsizei(std::size(attachments)), attachments);
+ }
+}
+
+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;
+
+ resetRhiDependentResources();
+}
+
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 && !isStereoEnabled())
+ targetBuffer = QOpenGLWidget::LeftBuffer;
+
+ if (!fbos[targetBuffer]) // could be completely offscreen, without ever getting a resize event
+ recreateFbos();
if (!inPaintGL)
render();
- if (resolvedFbo) {
- resolveSamples();
- resolvedFbo->bind();
- } else {
- q->makeCurrent();
+ setCurrentTargetBuffer(targetBuffer);
+ if (resolvedFbos[targetBuffer]) {
+ resolveSamplesForBuffer(targetBuffer);
+ resolvedFbos[targetBuffer]->bind();
}
const bool hasAlpha = q->format().hasAlpha();
@@ -927,8 +1080,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;
}
@@ -941,14 +1095,30 @@ void QOpenGLWidgetPrivate::initializeViewportFramebuffer()
q->setAutoFillBackground(true);
}
+bool QOpenGLWidgetPrivate::isStereoEnabled()
+{
+ Q_Q(QOpenGLWidget);
+ // Note that because this internally might use the requested format,
+ // then this can return a false positive on hardware where
+ // steroscopic rendering is not supported.
+ return q->format().stereo();
+}
+
+bool QOpenGLWidgetPrivate::toggleStereoTargetBuffer()
+{
+ return setCurrentTargetBuffer(currentTargetBuffer == QOpenGLWidget::LeftBuffer ?
+ QOpenGLWidget::RightBuffer :
+ QOpenGLWidget::LeftBuffer);
+}
+
void QOpenGLWidgetPrivate::resizeViewportFramebuffer()
{
Q_Q(QOpenGLWidget);
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();
}
}
@@ -1136,8 +1306,36 @@ 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();
+}
+
+/*!
+ Prepares for rendering OpenGL content for this widget by making the
+ context for the passed in buffer current and binding the framebuffer object in that
+ context.
+
+ \note This only makes sense to call when stereoscopic rendering is enabled.
+ Nothing will happen if the right buffer is requested when it's disabled.
+
+ It is not necessary to call this function in most cases, because it
+ is called automatically before invoking paintGL().
+
+ \since 6.5
+
+ \sa context(), paintGL(), doneCurrent()
+ */
+void QOpenGLWidget::makeCurrent(TargetBuffer targetBuffer)
+{
+ Q_D(QOpenGLWidget);
+ if (!d->initialized)
+ return;
+
+ // The FBO for the right buffer is only initialized when stereo is set
+ if (targetBuffer == TargetBuffer::RightBuffer && !format().stereo())
+ return;
+
+ d->setCurrentTargetBuffer(targetBuffer); // calls makeCurrent
}
/*!
@@ -1185,14 +1383,36 @@ 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.
+
+ Calling this overload only makes sense if \l QSurfaceFormat::StereoBuffers is enabled
+ and supported by the hardware. If not, this method will return the default buffer.
+
+ \note The framebuffer object belongs to the context returned by context()
+ and may not be accessible from other contexts. 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;
}
/*!
This virtual function is called once before the first call to
paintGL() or resizeGL(). Reimplement it in a subclass.
- This function should set up any required OpenGL resources and state.
+ This function should set up any required OpenGL resources.
There is no need to call makeCurrent() because this has already been
done when this function is called. Note however that the framebuffer
@@ -1234,10 +1454,32 @@ void QOpenGLWidget::resizeGL(int w, int h)
other state is set and no clearing or drawing is performed by the
framework.
- \sa initializeGL(), resizeGL()
+ The default implementation performs a glClear(). Subclasses are not expected
+ to invoke the base class implementation and should perform clearing on their
+ own.
+
+ \note To ensure portability, do not expect that state set in initializeGL()
+ persists. Rather, set all necessary state, for example, by calling
+ glEnable(), in paintGL(). This is because some platforms, such as WebAssembly
+ with WebGL, may have limitations on OpenGL contexts in some situations, which
+ can lead to using the context used with the QOpenGLWidget for other purposes
+ as well.
+
+ 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()
{
+ Q_D(QOpenGLWidget);
+ if (d->initialized)
+ d->context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
/*!
@@ -1263,7 +1505,10 @@ 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();
resizeGL(width(), height());
d->sendPaintEvent(QRect(QPoint(0, 0), size()));
}
@@ -1305,6 +1550,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
@@ -1359,15 +1637,9 @@ int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
else
return qRound(dpmy * 0.0254);
case PdmDevicePixelRatio:
- if (window)
- return int(window->devicePixelRatio());
- else
- return 1.0;
+ return QWidget::metric(metric);
case PdmDevicePixelRatioScaled:
- if (window)
- return int(window->devicePixelRatio() * devicePixelRatioFScale());
- else
- return int(devicePixelRatioFScale());
+ return QWidget::metric(metric);
default:
qWarning("QOpenGLWidget::metric(): unknown metric %d", metric);
return 0;
@@ -1404,6 +1676,20 @@ QPaintEngine *QOpenGLWidget::paintEngine() const
return d->paintDevice->paintEngine();
}
+
+bool QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
+{
+ Q_Q(QOpenGLWidget);
+
+ if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
+ return false;
+
+ currentTargetBuffer = targetBuffer;
+ q->makeCurrent();
+
+ return true;
+}
+
/*!
\reimp
*/
@@ -1422,18 +1708,18 @@ bool QOpenGLWidget::event(QEvent *e)
if (isHidden())
break;
Q_FALLTHROUGH();
- case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too
- if (d->initialized && !d->wrapperTexture && window()->windowHandle()) {
+ case QEvent::Show: // reparenting may not lead to a resize so reinitialize on Show too
+ 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))
d->reset();
}
- if (QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(window())->maybeRepaintManager()) {
- if (!d->initialized && !size().isEmpty() && repaintManager->rhi()) {
+ if (d->rhi()) {
+ if (!d->initialized && !size().isEmpty()) {
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.
@@ -1442,9 +1728,9 @@ bool QOpenGLWidget::event(QEvent *e)
}
}
break;
- case QEvent::ScreenChangeInternal:
+ case QEvent::DevicePixelRatioChange:
if (d->initialized && d->paintDevice->devicePixelRatio() != devicePixelRatio())
- d->recreateFbo();
+ d->recreateFbos();
break;
default:
break;