Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qopenglwidget.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include <QtGui/QOpenGLContext>
7#include <QtGui/QOffscreenSurface>
8#include <QtGui/QOpenGLFunctions>
9#include <QtGui/QWindow>
10#include <QtGui/QGuiApplication>
11#include <QtGui/QScreen>
12#include <QtGui/qpa/qplatformwindow.h>
13#include <QtGui/qpa/qplatformintegration.h>
14#include <QtOpenGL/QOpenGLFramebufferObject>
15#include <QtOpenGL/QOpenGLPaintDevice>
16
17#include <QtGui/private/qguiapplication_p.h>
18#include <QtGui/private/qopenglextensions_p.h>
19#include <QtGui/private/qfont_p.h>
20#include <QtGui/private/qopenglcontext_p.h>
21#include <QtOpenGL/private/qopenglframebufferobject_p.h>
22#include <QtOpenGL/private/qopenglpaintdevice_p.h>
23
24#include <QtWidgets/private/qwidget_p.h>
25#include <QtWidgets/private/qwidgetrepaintmanager_p.h>
26
27#include <rhi/qrhi.h>
28
30
31/*!
32 \class QOpenGLWidget
33 \inmodule QtOpenGLWidgets
34 \since 5.4
35
36 \brief The QOpenGLWidget class is a widget for rendering OpenGL graphics.
37
38 QOpenGLWidget provides functionality for displaying OpenGL graphics
39 integrated into a Qt application. It is very simple to use: Make
40 your class inherit from it and use the subclass like any other
41 QWidget, except that you have the choice between using QPainter and
42 standard OpenGL rendering commands.
43
44 QOpenGLWidget provides three convenient virtual functions that you
45 can reimplement in your subclass to perform the typical OpenGL
46 tasks:
47
48 \list
49 \li paintGL() - Renders the OpenGL scene. Gets called whenever the widget
50 needs to be updated.
51 \li resizeGL() - Sets up the OpenGL viewport, projection, etc. Gets
52 called whenever the widget has been resized (and also when it
53 is shown for the first time because all newly created widgets get a
54 resize event automatically).
55 \li initializeGL() - Sets up the OpenGL resources and state. Gets called
56 once before the first time resizeGL() or paintGL() is called.
57 \endlist
58
59 If you need to trigger a repaint from places other than paintGL() (a
60 typical example is when using \l{QChronoTimer}{timers} to animate scenes),
61 you should call the widget's update() function to schedule an update.
62
63 Your widget's OpenGL rendering context is made current when
64 paintGL(), resizeGL(), or initializeGL() is called. If you need to
65 call the standard OpenGL API functions from other places (e.g. in
66 your widget's constructor or in your own paint functions), you
67 must call makeCurrent() first.
68
69 All rendering happens into an OpenGL framebuffer
70 object. makeCurrent() ensure that it is bound in the context. Keep
71 this in mind when creating and binding additional framebuffer
72 objects in the rendering code in paintGL(). Never re-bind the
73 framebuffer with ID 0. Instead, call defaultFramebufferObject() to
74 get the ID that should be bound.
75
76 QOpenGLWidget allows using different OpenGL versions and profiles
77 when the platform supports it. Just set the requested format via
78 setFormat(). Keep in mind however that having multiple QOpenGLWidget
79 instances in the same window requires that they all use the same
80 format, or at least formats that do not make the contexts
81 non-sharable. To overcome this issue, prefer using
82 QSurfaceFormat::setDefaultFormat() instead of setFormat().
83
84 \note Calling QSurfaceFormat::setDefaultFormat() before constructing
85 the QApplication instance is mandatory on some platforms (for example,
86 \macos) when an OpenGL core profile context is requested. This is to
87 ensure that resource sharing between contexts stays functional as all
88 internal contexts are created using the correct version and profile.
89
90 \section1 Painting Techniques
91
92 As described above, subclass QOpenGLWidget to render pure 3D content in the
93 following way:
94
95 \list
96
97 \li Reimplement the initializeGL() and resizeGL() functions to
98 set up the OpenGL state and provide a perspective transformation.
99
100 \li Reimplement paintGL() to paint the 3D scene, calling only
101 OpenGL functions.
102
103 \endlist
104
105 It is also possible to draw 2D graphics onto a QOpenGLWidget subclass using QPainter:
106
107 \list
108
109 \li In paintGL(), instead of issuing OpenGL commands, construct a QPainter
110 object for use on the widget.
111
112 \li Draw primitives using QPainter's member functions.
113
114 \li Direct OpenGL commands can still be issued. However, you must make sure
115 these are enclosed by a call to the painter's beginNativePainting() and
116 endNativePainting().
117
118 \endlist
119
120 When performing drawing using QPainter only, it is also possible to perform
121 the painting like it is done for ordinary widgets: by reimplementing paintEvent().
122
123 \list
124
125 \li Reimplement the paintEvent() function.
126
127 \li Construct a QPainter object targeting the widget. Either pass the widget to the
128 constructor or the QPainter::begin() function.
129
130 \li Draw primitives using QPainter's member functions.
131
132 \li Painting finishes then the QPainter instance is destroyed. Alternatively,
133 call QPainter::end() explicitly.
134
135 \endlist
136
137 \section1 OpenGL Function Calls, Headers and QOpenGLFunctions
138
139 When making OpenGL function calls, it is strongly recommended to avoid calling
140 the functions directly. Instead, prefer using QOpenGLFunctions (when making
141 portable applications) or the versioned variants (for example,
142 QOpenGLFunctions_3_2_Core and similar, when targeting modern, desktop-only
143 OpenGL). This way the application will work correctly in all Qt build
144 configurations, including the ones that perform dynamic OpenGL implementation
145 loading which means applications are not directly linking to an GL
146 implementation and thus direct function calls are not feasible.
147
148 In paintGL() the current context is always accessible by calling
149 QOpenGLContext::currentContext(). From this context an already initialized,
150 ready-to-be-used QOpenGLFunctions instance is retrievable by calling
151 QOpenGLContext::functions(). An alternative to prefixing every GL call is to
152 inherit from QOpenGLFunctions and call
153 QOpenGLFunctions::initializeOpenGLFunctions() in initializeGL().
154
155 As for the OpenGL headers, note that in most cases there will be no need to
156 directly include any headers like GL.h. The OpenGL-related Qt headers will
157 include qopengl.h which will in turn include an appropriate header for the
158 system. This might be an OpenGL ES 3.x or 2.0 header, the highest version that
159 is available, or a system-provided gl.h. In addition, a copy of the extension
160 headers (called glext.h on some systems) is provided as part of Qt both for
161 OpenGL and OpenGL ES. These will get included automatically on platforms where
162 feasible. This means that constants and function pointer typedefs from ARB,
163 EXT, OES extensions are automatically available.
164
165 \section1 Code Examples
166
167 To get started, the simplest QOpenGLWidget subclass could look like the following:
168
169 \snippet code/doc_gui_widgets_qopenglwidget.cpp 0
170
171 Alternatively, the prefixing of each and every OpenGL call can be avoided by deriving
172 from QOpenGLFunctions instead:
173
174 \snippet code/doc_gui_widgets_qopenglwidget.cpp 1
175
176 To get a context compatible with a given OpenGL version or profile, or to
177 request depth and stencil buffers, call setFormat():
178
179 \snippet code/doc_gui_widgets_qopenglwidget.cpp 2
180
181 \note It is up to the application to ensure depth and stencil buffers are
182 requested from the underlying windowing system interface. Without requesting
183 a non-zero depth buffer size there is no guarantee that a depth buffer will
184 be available, and as a result depth testing related OpenGL operations may
185 fail to function as expected. Commonly used depth and stencil buffer size
186 requests are 24 and 8, respectively.
187
188 With OpenGL 3.0+ contexts, when portability is not important, the versioned
189 QOpenGLFunctions variants give easy access to all the modern OpenGL functions
190 available in a given version:
191
192 \snippet code/doc_gui_widgets_qopenglwidget.cpp 3
193
194 As described above, it is simpler and more robust to set the requested format
195 globally so that it applies to all windows and contexts during the lifetime of
196 the application. Below is an example of this:
197
198 \snippet code/doc_gui_widgets_qopenglwidget.cpp 6
199
200 \section1 Multisampling
201
202 To enable multisampling, set the number of requested samples on the
203 QSurfaceFormat that is passed to setFormat(). On systems that do not support
204 it the request may get ignored.
205
206 Multisampling support requires support for multisampled renderbuffers and
207 framebuffer blits. On OpenGL ES 2.0 implementations it is likely that these
208 will not be present. This means that multisampling will not be available. With
209 modern OpenGL versions and OpenGL ES 3.0 and up this is usually not a problem
210 anymore.
211
212 \section1 Threading
213
214 Performing offscreen rendering on worker threads, for example to generate
215 textures that are then used in the GUI/main thread in paintGL(), are supported
216 by exposing the widget's QOpenGLContext so that additional contexts sharing
217 with it can be created on each thread.
218
219 Drawing directly to the QOpenGLWidget's framebuffer outside the GUI/main
220 thread is possible by reimplementing paintEvent() to do nothing. The context's
221 thread affinity has to be changed via QObject::moveToThread(). After that,
222 makeCurrent() and doneCurrent() are usable on the worker thread. Be careful to
223 move the context back to the GUI/main thread afterwards.
224
225 Triggering a buffer swap just for the QOpenGLWidget is not possible since there
226 is no real, onscreen native surface for it. It is up to the widget stack to
227 manage composition and buffer swaps on the gui thread. When a thread is done
228 updating the framebuffer, call update() \b{on the GUI/main thread} to
229 schedule composition.
230
231 Extra care has to be taken to avoid using the framebuffer when the GUI/main
232 thread is performing compositing. The signals aboutToCompose() and
233 frameSwapped() will be emitted when the composition is starting and
234 ending. They are emitted on the GUI/main thread. This means that by using a
235 direct connection aboutToCompose() can block the GUI/main thread until the
236 worker thread has finished its rendering. After that, the worker thread must
237 perform no further rendering until the frameSwapped() signal is emitted. If
238 this is not acceptable, the worker thread has to implement a double buffering
239 mechanism. This involves drawing using an alternative render target, that is
240 fully controlled by the thread, e.g. an additional framebuffer object, and
241 blitting to the QOpenGLWidget's framebuffer at a suitable time.
242
243 \section1 Context Sharing
244
245 When multiple QOpenGLWidgets are added as children to the same top-level
246 widget, their contexts will share with each other. This does not apply for
247 QOpenGLWidget instances that belong to different windows.
248
249 This means that all QOpenGLWidgets in the same window can access each other's
250 sharable resources, like textures, and there is no need for an extra "global
251 share" context.
252
253 To set up sharing between QOpenGLWidget instances belonging to different
254 windows, set the Qt::AA_ShareOpenGLContexts application attribute before
255 instantiating QApplication. This will trigger sharing between all
256 QOpenGLWidget instances without any further steps.
257
258 Creating extra QOpenGLContext instances that share resources like textures
259 with the QOpenGLWidget's context is also possible. Simply pass the pointer
260 returned from context() to QOpenGLContext::setShareContext() before calling
261 QOpenGLContext::create(). The resulting context can also be used on a
262 different thread, allowing threaded generation of textures and asynchronous
263 texture uploads.
264
265 Note that QOpenGLWidget expects a standard conformant implementation of
266 resource sharing when it comes to the underlying graphics drivers. For
267 example, some drivers, in particular for mobile and embedded hardware, have
268 issues with setting up sharing between an existing context and others that are
269 created later. Some other drivers may behave in unexpected ways when trying to
270 utilize shared resources between different threads.
271
272 \section1 Resource Initialization and Cleanup
273
274 The QOpenGLWidget's associated OpenGL context is guaranteed to be current
275 whenever initializeGL() and paintGL() are invoked. Do not attempt to create
276 OpenGL resources before initializeGL() is called. For example, attempting to
277 compile shaders, initialize vertex buffer objects or upload texture data will
278 fail when done in a subclass's constructor. These operations must be deferred
279 to initializeGL(). Some of Qt's OpenGL helper classes, like QOpenGLBuffer or
280 QOpenGLVertexArrayObject, have a matching deferred behavior: they can be
281 instantiated without a context, but all initialization is deferred until a
282 create(), or similar, call. This means that they can be used as normal
283 (non-pointer) member variables in a QOpenGLWidget subclass, but the create()
284 or similar function can only be called from initializeGL(). Be aware however
285 that not all classes are designed like this. When in doubt, make the member
286 variable a pointer and create and destroy the instance dynamically in
287 initializeGL() and the destructor, respectively.
288
289 Releasing the resources also needs the context to be current. Therefore
290 destructors that perform such cleanup are expected to call makeCurrent()
291 before moving on to destroy any OpenGL resources or wrappers. Avoid deferred
292 deletion via \l{QObject::deleteLater()}{deleteLater()} or the parenting
293 mechanism of QObject. There is no guarantee the correct context will be
294 current at the time the instance in question is really destroyed.
295
296 A typical subclass will therefore often look like the following when it comes
297 to resource initialization and destruction:
298
299 \snippet code/doc_gui_widgets_qopenglwidget.cpp 4
300
301 This works for most cases, but not fully ideal as a generic solution. When
302 the widget is reparented so that it ends up in an entirely different
303 top-level window, something more is needed: by connecting to the
304 \l{QOpenGLContext::aboutToBeDestroyed()}{aboutToBeDestroyed()} signal of
305 QOpenGLContext, cleanup can be performed whenever the OpenGL context is about
306 to be released.
307
308 \note For widgets that change their associated top-level window multiple
309 times during their lifetime, a combined cleanup approach, as demonstrated in
310 the code snippet below, is essential. Whenever the widget or a parent of it
311 gets reparented so that the top-level window becomes different, the widget's
312 associated context is destroyed and a new one is created. This is then
313 followed by a call to initializeGL() where all OpenGL resources must get
314 reinitialized. Due to this the only option to perform proper cleanup is to
315 connect to the context's aboutToBeDestroyed() signal. Note that the context
316 in question may not be the current one when the signal gets emitted.
317 Therefore it is good practice to call makeCurrent() in the connected slot.
318 Additionally, the same cleanup steps must be performed from the derived
319 class' destructor, since the slot or lambda connected to the signal may not
320 invoked when the widget is being destroyed.
321
322 \snippet code/doc_gui_widgets_qopenglwidget.cpp 5
323
324 \note When Qt::AA_ShareOpenGLContexts is set, the widget's context never
325 changes, not even when reparenting because the widget's associated texture is
326 going to be accessible also from the new top-level's context. Therefore,
327 acting on the aboutToBeDestroyed() signal of the context is not mandatory
328 with this flag set.
329
330 Proper cleanup is especially important due to context sharing. Even though
331 each QOpenGLWidget's associated context is destroyed together with the
332 QOpenGLWidget, the sharable resources in that context, like textures, will
333 stay valid until the top-level window, in which the QOpenGLWidget lived, is
334 destroyed. Additionally, settings like Qt::AA_ShareOpenGLContexts and some Qt
335 modules may trigger an even wider scope for sharing contexts, potentially
336 leading to keeping the resources in question alive for the entire lifetime of
337 the application. Therefore the safest and most robust is always to perform
338 explicit cleanup for all resources and resource wrappers used in the
339 QOpenGLWidget.
340
341 \section1 Limitations and Other Considerations
342
343 Putting other widgets underneath and making the QOpenGLWidget transparent will
344 not lead to the expected results: The widgets underneath will not be
345 visible. This is because in practice the QOpenGLWidget is drawn before all
346 other regular, non-OpenGL widgets, and so see-through type of solutions are
347 not feasible. Other type of layouts, like having widgets on top of the
348 QOpenGLWidget, will function as expected.
349
350 When absolutely necessary, this limitation can be overcome by setting the
351 Qt::WA_AlwaysStackOnTop attribute on the QOpenGLWidget. Be aware however that
352 this breaks stacking order, for example it will not be possible to have other
353 widgets on top of the QOpenGLWidget, so it should only be used in situations
354 where a semi-transparent QOpenGLWidget with other widgets visible underneath
355 is required.
356
357 Note that this does not apply when there are no other widgets underneath and
358 the intention is to have a semi-transparent window. In that case the
359 traditional approach of setting Qt::WA_TranslucentBackground
360 on the top-level window is sufficient. Note that if the transparent areas are
361 only desired in the QOpenGLWidget, then Qt::WA_NoSystemBackground will need
362 to be turned back to \c false after enabling Qt::WA_TranslucentBackground.
363 Additionally, requesting an alpha channel for the QOpenGLWidget's context via
364 setFormat() may be necessary too, depending on the system.
365
366 QOpenGLWidget supports multiple update behaviors, just like QOpenGLWindow. In
367 preserved mode the rendered content from the previous paintGL() call is
368 available in the next one, allowing incremental rendering. In non-preserved
369 mode the content is lost and paintGL() implementations are expected to redraw
370 everything in the view.
371
372 Before Qt 5.5 the default behavior of QOpenGLWidget was to preserve the
373 rendered contents between paintGL() calls. Since Qt 5.5 the default behavior
374 is non-preserved because this provides better performance and the majority of
375 applications have no need for the previous content. This also resembles the
376 semantics of an OpenGL-based QWindow and matches the default behavior of
377 QOpenGLWindow in that the color and ancillary buffers are invalidated for
378 each frame. To restore the preserved behavior, call setUpdateBehavior() with
379 \c PartialUpdate.
380
381 \note When dynamically adding a QOpenGLWidget into a widget hierarchy, e.g.
382 by parenting a new QOpenGLWidget to a widget where the corresponding
383 top-level widget is already shown on screen, the associated native window may
384 get implicitly destroyed and recreated if the QOpenGLWidget is the first of
385 its kind within its window. This is because the window type changes from
386 \l{QSurface::RasterSurface}{RasterSurface} to
387 \l{QSurface::OpenGLSurface}{OpenGLSurface} and that has platform-specific
388 implications. This behavior is new in Qt 6.4.
389
390 Once a QOpenGLWidget is added to a widget hierarchy, the contents of the
391 top-level window is flushed via OpenGL-based rendering. Widgets other than
392 the QOpenGLWidget continue to draw their content using a software-based
393 painter, but the final composition is done through the 3D API.
394
395 \note Displaying a QOpenGLWidget requires an alpha channel in the associated
396 top-level window's backing store due to the way composition with other
397 QWidget-based content works. If there is no alpha channel, the content
398 rendered by the QOpenGLWidget will not be visible. This can become
399 particularly relevant on Linux/X11 in remote display setups (such as, with
400 Xvnc), when using a color depth lower than 24. For example, a color depth of
401 16 will typically map to using a backing store image with the format
402 QImage::Format_RGB16 (RGB565), leaving no room for an alpha
403 channel. Therefore, if experiencing problems with getting the contents of a
404 QOpenGLWidget composited correctly with other the widgets in the window, make
405 sure the server (such as, vncserver) is configured with a 24 or 32 bit depth
406 instead of 16.
407
408 \section1 Alternatives
409
410 Adding a QOpenGLWidget into a window turns on OpenGL-based
411 compositing for the entire window. In some special cases this may
412 not be ideal, and the old QGLWidget-style behavior with a separate,
413 native child window is desired. Desktop applications that understand
414 the limitations of this approach (for example when it comes to
415 overlaps, transparency, scroll views and MDI areas), can use
416 QOpenGLWindow with QWidget::createWindowContainer(). This is a
417 modern alternative to QGLWidget and is faster than QOpenGLWidget due
418 to the lack of the additional composition step. It is strongly
419 recommended to limit the usage of this approach to cases where there
420 is no other choice. Note that this option is not suitable for most
421 embedded and mobile platforms, and it is known to have issues on
422 certain desktop platforms (e.g. \macos) too. The stable,
423 cross-platform solution is always QOpenGLWidget.
424
425
426 \section1 Stereoscopic rendering
427
428 Starting from 6.5 QOpenGLWidget has support for stereoscopic rendering.
429 To enable it, set the QSurfaceFormat::StereoBuffers flag
430 globally before the window is created, using QSurfaceFormat::SetDefaultFormat().
431
432 \note Using setFormat() will not necessarily work because of how the flag is
433 handled internally.
434
435 This will trigger paintGL() to be called twice each frame,
436 once for each QOpenGLWidget::TargetBuffer. In paintGL(), call
437 currentTargetBuffer() to query which one is currently being drawn to.
438
439 \note For more control over the left and right color buffers, consider using
440 QOpenGLWindow + QWidget::createWindowContainer() instead.
441
442 \note This type of 3D rendering has certain hardware requirements,
443 like the graphics card needs to be setup with stereo support.
444
445 \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other
446 countries.}
447
448 \sa QOpenGLFunctions, QOpenGLWindow, Qt::AA_ShareOpenGLContexts, UpdateBehavior
449*/
450
451/*!
452 \fn void QOpenGLWidget::aboutToCompose()
453
454 This signal is emitted when the widget's top-level window is about to begin
455 composing the textures of its QOpenGLWidget children and the other widgets.
456*/
457
458/*!
459 \fn void QOpenGLWidget::frameSwapped()
460
461 This signal is emitted after the widget's top-level window has finished
462 composition and returned from its potentially blocking
463 QOpenGLContext::swapBuffers() call.
464*/
465
466/*!
467 \fn void QOpenGLWidget::aboutToResize()
468
469 This signal is emitted when the widget's size is changed and therefore the
470 framebuffer object is going to be recreated.
471*/
472
473/*!
474 \fn void QOpenGLWidget::resized()
475
476 This signal is emitted right after the framebuffer object has been recreated
477 due to resizing the widget.
478*/
479
480/*!
481 \enum QOpenGLWidget::TargetBuffer
482 \since 6.5
483
484 Specifies the buffer to use when stereoscopic rendering is enabled, which is
485 toggled by setting \l QSurfaceFormat::StereoBuffers.
486
487 \note LeftBuffer is always the default and used as fallback value when
488 stereoscopic rendering is disabled or not supported by the graphics driver.
489
490 \value LeftBuffer
491 \value RightBuffer
492 */
493
494/*!
495 \enum QOpenGLWidget::UpdateBehavior
496 \since 5.5
497
498 This enum describes the update semantics of QOpenGLWidget.
499
500 \value NoPartialUpdate QOpenGLWidget will discard the
501 contents of the color buffer and the ancillary buffers after the
502 QOpenGLWidget is rendered to screen. This is the same behavior that can be
503 expected by calling QOpenGLContext::swapBuffers with a default opengl
504 enabled QWindow as the argument. NoPartialUpdate can have some performance
505 benefits on certain hardware architectures common in the mobile and
506 embedded space when a framebuffer object is used as the rendering target.
507 The framebuffer object is invalidated between frames with
508 glInvalidateFramebuffer (if supported), or, as fallbacks,
509 glDiscardFramebufferEXT (if supported) or a call to glClear.
510
511 \value PartialUpdate The framebuffer objects color buffer and ancillary
512 buffers are not invalidated between frames.
513
514 \sa updateBehavior(), setUpdateBehavior()
515*/
516
529
537
539{
540 Q_DECLARE_PUBLIC(QOpenGLWidget)
541public:
543
544 void reset();
548
551
552 QPlatformBackingStoreRhiConfig rhiConfig() const override { return { QPlatformBackingStoreRhiConfig::OpenGL }; }
553
555 void render();
556
557 static constexpr GLenum gl_color_attachment0 = 0x8CE0; // GL_COLOR_ATTACHMENT0
558 static constexpr GLenum gl_depth_attachment = 0x8D00; // GL_DEPTH_ATTACHMENT
559 static constexpr GLenum gl_stencil_attachment = 0x8D20; // GL_STENCIL_ATTACHMENT
560 static constexpr GLenum gl_depth_stencil_attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT
561
564
566
567 bool setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
568 QImage grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer);
579
580 void resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer);
581
583 QRhiTexture *wrapperTextures[2] = {};
592 bool initialized = false;
593 bool fakeHidden = false;
595 bool hasBeenComposed = false;
596 bool flushPending = false;
597 bool inPaintGL = false;
598 bool isViewport = false;
600};
601
603{
604 // NB! autoFillBackground is and must be false by default. Otherwise we would clear on
605 // every QPainter begin() which is not desirable. This is only for legacy use cases,
606 // like using QOpenGLWidget as the viewport of a graphics view, that expect clearing
607 // with the palette's background color.
608 if (w->autoFillBackground()) {
609 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
610 if (w->testAttribute(Qt::WA_TranslucentBackground)) {
611 f->glClearColor(0, 0, 0, 0);
612 } else {
613 QColor c = w->palette().brush(w->backgroundRole()).color();
614 float alpha = c.alphaF();
615 f->glClearColor(c.redF() * alpha, c.greenF() * alpha, c.blueF() * alpha, alpha);
616 }
617 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
618 }
619}
620
622{
623 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(w));
624 if (!wd->initialized)
625 return;
626
627 if (!wd->inPaintGL)
628 QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = 0;
629}
630
632{
633 QOpenGLWidgetPaintDevicePrivate *d = static_cast<QOpenGLWidgetPaintDevicePrivate *>(d_ptr.data());
634 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(d->w));
635 if (!wd->initialized)
636 return;
637
639
640 if (QOpenGLContext::currentContext() != wd->context)
641 d->w->makeCurrent();
642 else
643 wd->fbos[wd->currentTargetBuffer]->bind();
644
645
646 if (!wd->inPaintGL)
647 QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = wd->fbos[wd->currentTargetBuffer]->handle();
648
649 // When used as a viewport, drawing is done via opening a QPainter on the widget
650 // without going through paintEvent(). We will have to make sure a glFlush() is done
651 // before the texture is accessed also in this case.
652 wd->flushPending = true;
653}
654
656{
657 return { wrapperTextures[QOpenGLWidget::LeftBuffer], wrapperTextures[QOpenGLWidget::RightBuffer] };
658}
659
660#ifndef GL_SRGB
661#define GL_SRGB 0x8C40
662#endif
663#ifndef GL_SRGB8
664#define GL_SRGB8 0x8C41
665#endif
666#ifndef GL_SRGB_ALPHA
667#define GL_SRGB_ALPHA 0x8C42
668#endif
669#ifndef GL_SRGB8_ALPHA8
670#define GL_SRGB8_ALPHA8 0x8C43
671#endif
672
674{
675 QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags();
676 switch (textureFormat) {
677 case GL_SRGB:
678 case GL_SRGB8:
679 case GL_SRGB_ALPHA:
680 case GL_SRGB8_ALPHA8:
681 flags |= QPlatformTextureList::TextureIsSrgb;
682 break;
683 default:
684 break;
685 }
686 return flags;
687}
688
690{
691 Q_Q(QOpenGLWidget);
692
693 // Destroy the OpenGL resources first. These need the context to be current.
694 if (initialized)
695 q->makeCurrent();
696
697 delete paintDevice;
698 paintDevice = nullptr;
699
701
702 if (initialized)
703 q->doneCurrent();
704
705 // Delete the context first, then the surface. Slots connected to
706 // the context's aboutToBeDestroyed() may still call makeCurrent()
707 // to perform some cleanup.
708 delete context;
709 context = nullptr;
710 delete surface;
711 surface = nullptr;
713}
714
716{
717 // QRhi resource created from the QRhi. These must be released whenever the
718 // widget gets associated with a different QRhi, even when all OpenGL
719 // contexts share resources.
720
721 delete wrapperTextures[0];
722 wrapperTextures[0] = nullptr;
723
724 if (isStereoEnabled()) {
725 delete wrapperTextures[1];
726 wrapperTextures[1] = nullptr;
727 }
728}
729
731{
732 Q_Q(QOpenGLWidget);
733
734 emit q->aboutToResize();
735
736 context->makeCurrent(surface);
737
739
740 int samples = requestedSamples;
741 QOpenGLExtensions *extfuncs = static_cast<QOpenGLExtensions *>(context->functions());
742 if (!extfuncs->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample))
743 samples = 0;
744
745 QOpenGLFramebufferObjectFormat format;
746 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
747 format.setSamples(samples);
748 if (textureFormat)
749 format.setInternalTextureFormat(textureFormat);
750
751 const QSize deviceSize = q->size() * q->devicePixelRatio();
752 fbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
753 if (samples > 0)
754 resolvedFbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize);
755
756 const bool stereo = isStereoEnabled();
757
758 if (stereo) {
759 fbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
760 if (samples > 0)
761 resolvedFbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize);
762 }
763
764 textureFormat = fbos[QOpenGLWidget::LeftBuffer]->format().internalTextureFormat();
765
766 currentTargetBuffer = QOpenGLWidget::LeftBuffer;
767 fbos[currentTargetBuffer]->bind();
768 context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
770
771 if (stereo) {
772 currentTargetBuffer = QOpenGLWidget::RightBuffer;
773 fbos[currentTargetBuffer]->bind();
774 context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
776 currentTargetBuffer = QOpenGLWidget::LeftBuffer;
777 }
778
779 flushPending = true; // Make sure the FBO is initialized before use
780
781 paintDevice->setSize(deviceSize);
782 paintDevice->setDevicePixelRatio(q->devicePixelRatio());
783
784 emit q->resized();
785}
786
788{
789 Q_Q(QOpenGLWidget);
790
791 QRhi *rhi = QWidgetPrivate::rhi();
792
793 // If there is no rhi, because we are completely offscreen, then there's no wrapperTexture either
794 if (rhi && rhi->backend() == QRhi::OpenGLES2) {
795 const QSize deviceSize = q->size() * q->devicePixelRatio();
796 if (!wrapperTextures[currentTargetBuffer] || wrapperTextures[currentTargetBuffer]->pixelSize() != deviceSize) {
797 const uint textureId = resolvedFbos[currentTargetBuffer] ?
798 resolvedFbos[currentTargetBuffer]->texture()
799 : (fbos[currentTargetBuffer] ? fbos[currentTargetBuffer]->texture() : 0);
800 if (!wrapperTextures[currentTargetBuffer])
801 wrapperTextures[currentTargetBuffer] = rhi->newTexture(QRhiTexture::RGBA8, deviceSize, 1, QRhiTexture::RenderTarget);
802 else
803 wrapperTextures[currentTargetBuffer]->setPixelSize(deviceSize);
804 if (!wrapperTextures[currentTargetBuffer]->createFrom({textureId, 0 }))
805 qWarning("QOpenGLWidget: Failed to create wrapper texture");
806 }
807 }
808}
809
811{
812 Q_Q(QOpenGLWidget);
813 if (flushPending) {
814 flushPending = false;
815 q->makeCurrent();
816 static_cast<QOpenGLExtensions *>(context->functions())->flushShared();
817 }
818 hasBeenComposed = true;
819 emit q->aboutToCompose();
820}
821
823{
824 Q_Q(QOpenGLWidget);
825 emit q->frameSwapped();
826}
827
829{
830 Q_Q(QOpenGLWidget);
831 if (initialized)
832 return;
833
834 // If no global shared context get our toplevel's context with which we
835 // will share in order to make the texture usable by the underlying window's backingstore.
836 QWidget *tlw = q->window();
837
838 // Do not include the sample count. Requesting a multisampled context is not necessary
839 // since we render into an FBO, never to an actual surface. What's more, attempting to
840 // create a pbuffer with a multisampled config crashes certain implementations. Just
841 // avoid the entire hassle, the result is the same.
842 requestedSamples = requestedFormat.samples();
843 requestedFormat.setSamples(0);
844
845 QRhi *rhi = QWidgetPrivate::rhi();
846
847 // Could be that something else already initialized the window with some
848 // other graphics API for the QRhi, that's not good.
849 if (rhi && rhi->backend() != QRhi::OpenGLES2) {
850 qWarning("The top-level window is not using OpenGL for composition, '%s' is not compatible with QOpenGLWidget",
851 rhi->backendName());
852 return;
853 }
854
855 // If rhi or contextFromRhi is null, showing content on-screen will not work.
856 // However, offscreen rendering and grabFramebuffer() will stay fully functional.
857
858 QOpenGLContext *contextFromRhi = rhi ? static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles())->context : nullptr;
859
860 context = new QOpenGLContext;
861 context->setFormat(requestedFormat);
862
863 QOpenGLContext *shareContext = contextFromRhi ? contextFromRhi : QOpenGLContext::globalShareContext();
864 if (shareContext) {
865 context->setShareContext(shareContext);
866 context->setScreen(shareContext->screen());
867 }
868 if (Q_UNLIKELY(!context->create())) {
869 qWarning("QOpenGLWidget: Failed to create context");
870 return;
871 }
872
873 surface = new QOffscreenSurface;
874 surface->setFormat(context->format());
875 surface->setScreen(context->screen());
876 surface->create();
877
878 if (Q_UNLIKELY(!context->makeCurrent(surface))) {
879 qWarning("QOpenGLWidget: Failed to make context current");
880 return;
881 }
882
883 // Propagate settings that make sense only for the tlw. Note that this only
884 // makes sense for properties that get picked up even after the native
885 // window is created.
886 if (tlw->windowHandle()) {
887 QSurfaceFormat tlwFormat = tlw->windowHandle()->format();
888 if (requestedFormat.swapInterval() != tlwFormat.swapInterval()) {
889 // Most platforms will pick up the changed swap interval on the next
890 // makeCurrent or swapBuffers.
891 tlwFormat.setSwapInterval(requestedFormat.swapInterval());
892 tlw->windowHandle()->setFormat(tlwFormat);
893 }
894 if (requestedFormat.swapBehavior() != tlwFormat.swapBehavior()) {
895 tlwFormat.setSwapBehavior(requestedFormat.swapBehavior());
896 tlw->windowHandle()->setFormat(tlwFormat);
897 }
898 }
899
900 paintDevice = new QOpenGLWidgetPaintDevice(q);
901 paintDevice->setSize(q->size() * q->devicePixelRatio());
902 paintDevice->setDevicePixelRatio(q->devicePixelRatio());
903
904 initialized = true;
905
906 q->initializeGL();
907}
908
910{
911 resolveSamplesForBuffer(QOpenGLWidget::LeftBuffer);
912 resolveSamplesForBuffer(QOpenGLWidget::RightBuffer);
913}
914
915void QOpenGLWidgetPrivate::resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
916{
917 Q_Q(QOpenGLWidget);
918 if (resolvedFbos[targetBuffer]) {
919 q->makeCurrent(targetBuffer);
920 QRect rect(QPoint(0, 0), fbos[targetBuffer]->size());
921 QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[targetBuffer], rect, fbos[targetBuffer], rect);
922 flushPending = true;
923 }
924}
925
927{
928 Q_Q(QOpenGLWidget);
929
930 if (fakeHidden || !initialized)
931 return;
932
933 setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
934
935 QOpenGLContext *ctx = QOpenGLContext::currentContext();
936 if (!ctx) {
937 qWarning("QOpenGLWidget: No current context, cannot render");
938 return;
939 }
940
941 if (!fbos[QOpenGLWidget::LeftBuffer]) {
942 qWarning("QOpenGLWidget: No fbo, cannot render");
943 return;
944 }
945
946 const bool stereo = isStereoEnabled();
947 if (stereo) {
948 static bool warningGiven = false;
949 if (!fbos[QOpenGLWidget::RightBuffer] && !warningGiven) {
950 qWarning("QOpenGLWidget: Stereo is enabled, but no right buffer. Using only left buffer");
951 warningGiven = true;
952 }
953 }
954
955 if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
957
958 if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
959 setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
961 setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
962 }
963
964 hasBeenComposed = false;
965 }
966
967 QOpenGLFunctions *f = ctx->functions();
968 f->glViewport(0, 0, q->width() * q->devicePixelRatio(), q->height() * q->devicePixelRatio());
969 inPaintGL = true;
970
971#ifdef Q_OS_WASM
972 f->glDepthMask(GL_TRUE);
973#endif
974
975 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
976
977 f->glUseProgram(0);
978 f->glBindBuffer(GL_ARRAY_BUFFER, 0);
979 f->glEnable(GL_BLEND);
980
981 q->paintGL();
982 if (updateBehavior == QOpenGLWidget::NoPartialUpdate)
984
985 if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
986 setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
987 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
988 q->paintGL();
989 if (updateBehavior == QOpenGLWidget::NoPartialUpdate)
991 }
992 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
993
994 inPaintGL = false;
995 flushPending = true;
996}
997
999{
1000 QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
1001 if (f->hasOpenGLExtension(QOpenGLExtensions::DiscardFramebuffer)) {
1002 const GLenum attachments[] = {
1003 gl_color_attachment0,
1004 gl_depth_attachment,
1005 gl_stencil_attachment,
1006#ifdef Q_OS_WASM
1007 // webgl does not allow separate depth and stencil attachments
1008 // QTBUG-69913
1009 gl_depth_stencil_attachment
1010#endif
1011 };
1012 f->discardFramebuffer(GL_FRAMEBUFFER, GLsizei(std::size(attachments)), attachments);
1013 } else {
1014 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
1015 }
1016}
1017
1019{
1020 QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
1021 if (f->hasOpenGLExtension(QOpenGLExtensions::DiscardFramebuffer)) {
1022 const GLenum attachments[] = {
1023 gl_depth_attachment,
1024 gl_stencil_attachment,
1025#ifdef Q_OS_WASM
1026 // webgl does not allow separate depth and stencil attachments
1027 // QTBUG-69913
1028 gl_depth_stencil_attachment
1029#endif
1030 };
1031 f->discardFramebuffer(GL_FRAMEBUFFER, GLsizei(std::size(attachments)), attachments);
1032 }
1033}
1034
1036{
1037 delete fbos[QOpenGLWidget::LeftBuffer];
1038 fbos[QOpenGLWidget::LeftBuffer] = nullptr;
1039 delete resolvedFbos[QOpenGLWidget::LeftBuffer];
1040 resolvedFbos[QOpenGLWidget::LeftBuffer] = nullptr;
1041
1042 delete fbos[QOpenGLWidget::RightBuffer];
1043 fbos[QOpenGLWidget::RightBuffer] = nullptr;
1044 delete resolvedFbos[QOpenGLWidget::RightBuffer];
1045 resolvedFbos[QOpenGLWidget::RightBuffer] = nullptr;
1046
1048}
1049
1051{
1052 return grabFramebuffer(QOpenGLWidget::LeftBuffer);
1053}
1054
1055QImage QOpenGLWidgetPrivate::grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer)
1056{
1057 Q_Q(QOpenGLWidget);
1058
1060 if (!initialized)
1061 return QImage();
1062
1063 // The second fbo is only created when stereoscopic rendering is enabled
1064 // Just use the default one if not.
1065 if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
1066 targetBuffer = QOpenGLWidget::LeftBuffer;
1067
1068 if (!fbos[targetBuffer]) // could be completely offscreen, without ever getting a resize event
1070
1071 if (!inPaintGL && !paintDevice->paintingActive()) {
1072 if (isViewport) {
1073 // The content comes from the scroll area's paintEvent(), routed via
1074 // the viewport's event filter, not from paintGL(). QWidget::grab()
1075 // gets here while punching the hole, but the painting must still go
1076 // to the fbo, not to the target image.
1077 const bool wasInBackingStorePaint = std::exchange(inBackingStorePaint, false);
1078 const bool wasInPaintEvent = q->testAttribute(Qt::WA_WState_InPaintEvent);
1079 q->setAttribute(Qt::WA_WState_InPaintEvent);
1080 QPaintEvent e(q->rect());
1081 QCoreApplication::sendEvent(q, &e);
1082 q->setAttribute(Qt::WA_WState_InPaintEvent, wasInPaintEvent);
1083 inBackingStorePaint = wasInBackingStorePaint;
1084 } else {
1085 render();
1086 }
1087 }
1088
1089 setCurrentTargetBuffer(targetBuffer);
1090 if (resolvedFbos[targetBuffer]) {
1091 resolveSamplesForBuffer(targetBuffer);
1092 resolvedFbos[targetBuffer]->bind();
1093 }
1094
1095 const bool hasAlpha = q->format().hasAlpha();
1096 QImage res = qt_gl_read_framebuffer(q->size() * q->devicePixelRatio(), hasAlpha, hasAlpha);
1097 res.setDevicePixelRatio(q->devicePixelRatio());
1098
1099 // While we give no guarantees of what is going to be left bound, prefer the
1100 // multisample fbo instead of the resolved one. Clients may continue to
1101 // render straight after calling this function.
1102 if (resolvedFbos[targetBuffer]) {
1103 setCurrentTargetBuffer(targetBuffer);
1104 }
1105
1106 return res;
1107}
1108
1110{
1111 Q_Q(QOpenGLWidget);
1112 // Legacy behavior for compatibility with QGLWidget when used as a graphics view
1113 // viewport: enable clearing on each painter begin.
1114 q->setAutoFillBackground(true);
1115 isViewport = true;
1116}
1117
1119{
1120 Q_Q(QOpenGLWidget);
1121 // Note that because this internally might use the requested format,
1122 // then this can return a false positive on hardware where
1123 // steroscopic rendering is not supported.
1124 return q->format().stereo();
1125}
1126
1128{
1129 return setCurrentTargetBuffer(currentTargetBuffer == QOpenGLWidget::LeftBuffer ?
1130 QOpenGLWidget::RightBuffer :
1131 QOpenGLWidget::LeftBuffer);
1132}
1133
1135{
1136 Q_Q(QOpenGLWidget);
1137 if (!initialized)
1138 return;
1139
1140 if (!fbos[currentTargetBuffer] || q->size() * q->devicePixelRatio() != fbos[currentTargetBuffer]->size()) {
1142 q->update();
1143 }
1144}
1145
1146/*!
1147 Constructs a widget which is a child of \a parent, with widget flags set to \a f.
1148 */
1149QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f)
1150 : QWidget(*(new QOpenGLWidgetPrivate), parent, f)
1151{
1152 Q_D(QOpenGLWidget);
1153 if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)
1154 || !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)))
1155 qWarning("QOpenGLWidget is not supported on this platform.");
1156 else
1157 d->setRenderToTexture();
1158}
1159
1160/*!
1161 Destroys the QOpenGLWidget instance, freeing its resources.
1162
1163 The QOpenGLWidget's context is made current in the destructor, allowing for
1164 safe destruction of any child object that may need to release OpenGL
1165 resources belonging to the context provided by this widget.
1166
1167 \warning if you have objects wrapping OpenGL resources (such as
1168 QOpenGLBuffer, QOpenGLShaderProgram, etc.) as members of a OpenGLWidget
1169 subclass, you may need to add a call to makeCurrent() in that subclass'
1170 destructor as well. Due to the rules of C++ object destruction, those objects
1171 will be destroyed \e{before} calling this function (but after that the
1172 destructor of the subclass has run), therefore making the OpenGL context
1173 current in this function happens too late for their safe disposal.
1174
1175 \sa makeCurrent
1176*/
1177QOpenGLWidget::~QOpenGLWidget()
1178{
1179 // NB! resetting graphics resources must be done from this destructor,
1180 // *not* from the private class' destructor. This is due to how destruction
1181 // works and due to the QWidget dtor (for toplevels) destroying the repaint
1182 // manager and rhi before the (QObject) private gets destroyed. Hence must
1183 // do it here early on.
1184
1185 Q_D(QOpenGLWidget);
1186 d->reset();
1187}
1188
1189/*!
1190 Sets this widget's update behavior to \a updateBehavior.
1191 \since 5.5
1192*/
1193void QOpenGLWidget::setUpdateBehavior(UpdateBehavior updateBehavior)
1194{
1195 Q_D(QOpenGLWidget);
1196 d->updateBehavior = updateBehavior;
1197}
1198
1199/*!
1200 \return the update behavior of the widget.
1201 \since 5.5
1202*/
1203QOpenGLWidget::UpdateBehavior QOpenGLWidget::updateBehavior() const
1204{
1205 Q_D(const QOpenGLWidget);
1206 return d->updateBehavior;
1207}
1208
1209/*!
1210 Sets the requested surface \a format.
1211
1212 When the format is not explicitly set via this function, the format returned by
1213 QSurfaceFormat::defaultFormat() will be used. This means that when having multiple
1214 OpenGL widgets, individual calls to this function can be replaced by one single call to
1215 QSurfaceFormat::setDefaultFormat() before creating the first widget.
1216
1217 \note Requesting an alpha buffer via this function will not lead to the
1218 desired results when the intention is to make other widgets beneath visible.
1219 Instead, use Qt::WA_AlwaysStackOnTop to enable semi-transparent QOpenGLWidget
1220 instances with other widgets visible underneath. Keep in mind however that
1221 this breaks the stacking order, so it will no longer be possible to have
1222 other widgets on top of the QOpenGLWidget.
1223
1224 \sa format(), Qt::WA_AlwaysStackOnTop, QSurfaceFormat::setDefaultFormat()
1225 */
1226void QOpenGLWidget::setFormat(const QSurfaceFormat &format)
1227{
1228 Q_D(QOpenGLWidget);
1229 if (Q_UNLIKELY(d->initialized)) {
1230 qWarning("QOpenGLWidget: Already initialized, setting the format has no effect");
1231 return;
1232 }
1233
1234 d->requestedFormat = format;
1235}
1236
1237/*!
1238 Returns the context and surface format used by this widget and its toplevel
1239 window.
1240
1241 After the widget and its toplevel have both been created, resized and shown,
1242 this function will return the actual format of the context. This may differ
1243 from the requested format if the request could not be fulfilled by the
1244 platform. It is also possible to get larger color buffer sizes than
1245 requested.
1246
1247 When the widget's window and the related OpenGL resources are not yet
1248 initialized, the return value is the format that has been set via
1249 setFormat().
1250
1251 \sa setFormat(), context()
1252 */
1253QSurfaceFormat QOpenGLWidget::format() const
1254{
1255 Q_D(const QOpenGLWidget);
1256 return d->initialized ? d->context->format() : d->requestedFormat;
1257}
1258
1259/*!
1260 Sets a custom internal texture format of \a texFormat.
1261
1262 When working with sRGB framebuffers, it will be necessary to specify a
1263 format like \c{GL_SRGB8_ALPHA8}. This can be achieved by calling this
1264 function.
1265
1266 \note This function has no effect if called after the widget has already
1267 been shown and thus it performed initialization.
1268
1269 \note This function will typically have to be used in combination with a
1270 QSurfaceFormat::setColorSpace() call that sets the color space to
1271 QColorSpace::SRgb.
1272
1273 \since 5.10
1274 */
1275void QOpenGLWidget::setTextureFormat(GLenum texFormat)
1276{
1277 Q_D(QOpenGLWidget);
1278 if (Q_UNLIKELY(d->initialized)) {
1279 qWarning("QOpenGLWidget: Already initialized, setting the internal texture format has no effect");
1280 return;
1281 }
1282
1283 d->textureFormat = texFormat;
1284}
1285
1286/*!
1287 \return the active internal texture format if the widget has already
1288 initialized, the requested format if one was set but the widget has not yet
1289 been made visible, or \nullptr if setTextureFormat() was not called and the
1290 widget has not yet been made visible.
1291
1292 \since 5.10
1293 */
1294GLenum QOpenGLWidget::textureFormat() const
1295{
1296 Q_D(const QOpenGLWidget);
1297 return d->textureFormat;
1298}
1299
1300/*!
1301 \return \e true if the widget and OpenGL resources, like the context, have
1302 been successfully initialized. Note that the return value is always false
1303 until the widget is shown.
1304*/
1305bool QOpenGLWidget::isValid() const
1306{
1307 Q_D(const QOpenGLWidget);
1308 return d->initialized && d->context->isValid();
1309}
1310
1311/*!
1312 Prepares for rendering OpenGL content for this widget by making the
1313 corresponding context current and binding the framebuffer object in that
1314 context.
1315
1316 It is not necessary to call this function in most cases, because it
1317 is called automatically before invoking paintGL().
1318
1319 \sa context(), paintGL(), doneCurrent()
1320 */
1321void QOpenGLWidget::makeCurrent()
1322{
1323 Q_D(QOpenGLWidget);
1324 if (!d->initialized)
1325 return;
1326
1327 d->context->makeCurrent(d->surface);
1328
1329 if (d->fbos[d->currentTargetBuffer]) // there may not be one if we are in reset()
1330 d->fbos[d->currentTargetBuffer]->bind();
1331}
1332
1333/*!
1334 Prepares for rendering OpenGL content for this widget by making the
1335 context for the passed in buffer current and binding the framebuffer object in that
1336 context.
1337
1338 \note This only makes sense to call when stereoscopic rendering is enabled.
1339 Nothing will happen if the right buffer is requested when it's disabled.
1340
1341 It is not necessary to call this function in most cases, because it
1342 is called automatically before invoking paintGL().
1343
1344 \since 6.5
1345
1346 \sa context(), paintGL(), doneCurrent()
1347 */
1348void QOpenGLWidget::makeCurrent(TargetBuffer targetBuffer)
1349{
1350 Q_D(QOpenGLWidget);
1351 if (!d->initialized)
1352 return;
1353
1354 // The FBO for the right buffer is only initialized when stereo is set
1355 if (targetBuffer == TargetBuffer::RightBuffer && !format().stereo())
1356 return;
1357
1358 d->setCurrentTargetBuffer(targetBuffer); // calls makeCurrent
1359}
1360
1361/*!
1362 Releases the context.
1363
1364 It is not necessary to call this function in most cases, since the
1365 widget will make sure the context is bound and released properly
1366 when invoking paintGL().
1367 */
1368void QOpenGLWidget::doneCurrent()
1369{
1370 Q_D(QOpenGLWidget);
1371 if (!d->initialized)
1372 return;
1373
1374 d->context->doneCurrent();
1375}
1376
1377/*!
1378 \return The QOpenGLContext used by this widget or \c 0 if not yet initialized.
1379
1380 \note The context and the framebuffer object used by the widget changes when
1381 reparenting the widget via setParent().
1382
1383 \sa QOpenGLContext::setShareContext(), defaultFramebufferObject()
1384 */
1385QOpenGLContext *QOpenGLWidget::context() const
1386{
1387 Q_D(const QOpenGLWidget);
1388 return d->context;
1389}
1390
1391/*!
1392 \return The framebuffer object handle or \c 0 if not yet initialized.
1393
1394 \note The framebuffer object belongs to the context returned by context()
1395 and may not be accessible from other contexts.
1396
1397 \note The context and the framebuffer object used by the widget changes when
1398 reparenting the widget via setParent(). In addition, the framebuffer object
1399 changes on each resize.
1400
1401 \sa context()
1402 */
1403GLuint QOpenGLWidget::defaultFramebufferObject() const
1404{
1405 Q_D(const QOpenGLWidget);
1406 return d->fbos[TargetBuffer::LeftBuffer] ? d->fbos[TargetBuffer::LeftBuffer]->handle() : 0;
1407}
1408
1409/*!
1410 \return The framebuffer object handle of the specified target buffer or
1411 \c 0 if not yet initialized.
1412
1413 Calling this overload only makes sense if \l QSurfaceFormat::StereoBuffers is enabled
1414 and supported by the hardware. If not, this method will return the default buffer.
1415
1416 \note The framebuffer object belongs to the context returned by context()
1417 and may not be accessible from other contexts. The context and the framebuffer
1418 object used by the widget changes when reparenting the widget via setParent().
1419 In addition, the framebuffer object changes on each resize.
1420
1421 \since 6.5
1422
1423 \sa context()
1424 */
1425GLuint QOpenGLWidget::defaultFramebufferObject(TargetBuffer targetBuffer) const
1426{
1427 Q_D(const QOpenGLWidget);
1428 return d->fbos[targetBuffer] ? d->fbos[targetBuffer]->handle() : 0;
1429}
1430
1431/*!
1432 This virtual function is called once before the first call to
1433 paintGL() or resizeGL(). Reimplement it in a subclass.
1434
1435 This function should set up any required OpenGL resources.
1436
1437 There is no need to call makeCurrent() because this has already been
1438 done when this function is called. Note however that the framebuffer
1439 is not yet available at this stage, so avoid issuing draw calls from
1440 here. Defer such calls to paintGL() instead.
1441
1442 \sa paintGL(), resizeGL()
1443*/
1444void QOpenGLWidget::initializeGL()
1445{
1446}
1447
1448/*!
1449 This virtual function is called whenever the widget has been
1450 resized. Reimplement it in a subclass. The new size is passed in
1451 \a w and \a h.
1452
1453 There is no need to call makeCurrent() because this has already been
1454 done when this function is called. Additionally, the framebuffer is
1455 also bound.
1456
1457 \sa initializeGL(), paintGL()
1458*/
1459void QOpenGLWidget::resizeGL(int w, int h)
1460{
1461 Q_UNUSED(w);
1462 Q_UNUSED(h);
1463}
1464
1465/*!
1466 This virtual function is called whenever the widget needs to be
1467 painted. Reimplement it in a subclass.
1468
1469 There is no need to call makeCurrent() because this has already
1470 been done when this function is called.
1471
1472 Before invoking this function, the context and the framebuffer are
1473 bound, and the viewport is set up by a call to glViewport(). No
1474 other state is set and no clearing or drawing is performed by the
1475 framework.
1476
1477 The default implementation performs a glClear(). Subclasses are not expected
1478 to invoke the base class implementation and should perform clearing on their
1479 own.
1480
1481 \note To ensure portability, do not expect that state set in initializeGL()
1482 persists. Rather, set all necessary state, for example, by calling
1483 glEnable(), in paintGL(). This is because some platforms, such as WebAssembly
1484 with WebGL, may have limitations on OpenGL contexts in some situations, which
1485 can lead to using the context used with the QOpenGLWidget for other purposes
1486 as well.
1487
1488 When \l QSurfaceFormat::StereoBuffers is enabled, this function
1489 will be called twice - once for each buffer. Query what buffer is
1490 currently bound by calling currentTargetBuffer().
1491
1492 \note The framebuffer of each target will be drawn to even when
1493 stereoscopic rendering is not supported by the hardware.
1494 Only the left buffer will actually be visible in the window.
1495
1496 \sa initializeGL(), resizeGL(), currentTargetBuffer()
1497*/
1498void QOpenGLWidget::paintGL()
1499{
1500 Q_D(QOpenGLWidget);
1501 if (d->initialized)
1502 d->context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
1503}
1504
1505/*!
1506 Handles resize events that are passed in the \a e event parameter.
1507 Calls the virtual function resizeGL().
1508
1509 \note Avoid overriding this function in derived classes. If that is not
1510 feasible, make sure that QOpenGLWidget's implementation is invoked
1511 too. Otherwise the underlying framebuffer object and related resources will
1512 not get resized properly and will lead to incorrect rendering.
1513*/
1514void QOpenGLWidget::resizeEvent(QResizeEvent *e)
1515{
1516 Q_D(QOpenGLWidget);
1517
1518 if (e->size().isEmpty()) {
1519 d->fakeHidden = true;
1520 return;
1521 }
1522 d->fakeHidden = false;
1523
1524 d->initialize();
1525 if (!d->initialized)
1526 return;
1527
1528 d->recreateFbos();
1529 // Make sure our own context is current before invoking user overrides. If
1530 // the fbo was recreated then there's a chance something else is current now.
1531 makeCurrent();
1532 resizeGL(width(), height());
1533 d->sendPaintEvent(QRect(QPoint(0, 0), size()));
1534}
1535
1536/*!
1537 Handles paint events.
1538
1539 Calling QWidget::update() will lead to sending a paint event \a e,
1540 and thus invoking this function. (NB this is asynchronous and will
1541 happen at some point after returning from update()). This function
1542 will then, after some preparation, call the virtual paintGL() to
1543 update the contents of the QOpenGLWidget's framebuffer. The widget's
1544 top-level window will then composite the framebuffer's texture with
1545 the rest of the window.
1546*/
1547void QOpenGLWidget::paintEvent(QPaintEvent *e)
1548{
1549 Q_UNUSED(e);
1550 Q_D(QOpenGLWidget);
1551
1552 d->initialize();
1553 if (d->initialized) {
1554 d->ensureRhiDependentResources();
1555 if (updatesEnabled())
1556 d->render();
1557 }
1558}
1559
1560/*!
1561 Renders and returns a 32-bit RGB image of the framebuffer.
1562
1563 \note This is a potentially expensive operation because it relies on glReadPixels()
1564 to read back the pixels. This may be slow and can stall the GPU pipeline.
1565*/
1566QImage QOpenGLWidget::grabFramebuffer()
1567{
1568 Q_D(QOpenGLWidget);
1569 return d->grabFramebuffer();
1570}
1571
1572/*!
1573 Renders and returns a 32-bit RGB image of the framebuffer of the specified target buffer.
1574 This overload only makes sense to call when \l QSurfaceFormat::StereoBuffers is enabled.
1575 Grabbing the framebuffer of the right target buffer will return the default image
1576 if stereoscopic rendering is disabled or if not supported by the hardware.
1577
1578 \note This is a potentially expensive operation because it relies on glReadPixels()
1579 to read back the pixels. This may be slow and can stall the GPU pipeline.
1580
1581 \since 6.5
1582*/
1583QImage QOpenGLWidget::grabFramebuffer(TargetBuffer targetBuffer)
1584{
1585 Q_D(QOpenGLWidget);
1586 return d->grabFramebuffer(targetBuffer);
1587}
1588
1589/*!
1590 Returns the currently active target buffer. This will be the left buffer by default,
1591 the right buffer is only used when \l QSurfaceFormat::StereoBuffers is enabled.
1592 When stereoscopic rendering is enabled, this can be queried in paintGL() to know
1593 what buffer is currently in use. paintGL() will be called twice, once for each target.
1594
1595 \since 6.5
1596
1597 \sa paintGL()
1598*/
1599QOpenGLWidget::TargetBuffer QOpenGLWidget::currentTargetBuffer() const
1600{
1601 Q_D(const QOpenGLWidget);
1602 return d->currentTargetBuffer;
1603}
1604
1605/*!
1606 \reimp
1607*/
1608int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
1609{
1610 Q_D(const QOpenGLWidget);
1611 if (d->inBackingStorePaint)
1612 return QWidget::metric(metric);
1613
1614 auto window = d->windowHandle(QWidgetPrivate::WindowHandleMode::TopLevel);
1615 QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
1616
1617 const float dpmx = qt_defaultDpiX() * 100. / 2.54;
1618 const float dpmy = qt_defaultDpiY() * 100. / 2.54;
1619
1620 switch (metric) {
1621 case PdmWidth:
1622 return width();
1623 case PdmHeight:
1624 return height();
1625 case PdmDepth:
1626 return 32;
1627 case PdmWidthMM:
1628 if (screen)
1629 return width() * screen->physicalSize().width() / screen->geometry().width();
1630 else
1631 return width() * 1000 / dpmx;
1632 case PdmHeightMM:
1633 if (screen)
1634 return height() * screen->physicalSize().height() / screen->geometry().height();
1635 else
1636 return height() * 1000 / dpmy;
1637 case PdmNumColors:
1638 return 0;
1639 case PdmDpiX:
1640 if (screen)
1641 return qRound(screen->logicalDotsPerInchX());
1642 else
1643 return qRound(dpmx * 0.0254);
1644 case PdmDpiY:
1645 if (screen)
1646 return qRound(screen->logicalDotsPerInchY());
1647 else
1648 return qRound(dpmy * 0.0254);
1649 case PdmPhysicalDpiX:
1650 if (screen)
1651 return qRound(screen->physicalDotsPerInchX());
1652 else
1653 return qRound(dpmx * 0.0254);
1654 case PdmPhysicalDpiY:
1655 if (screen)
1656 return qRound(screen->physicalDotsPerInchY());
1657 else
1658 return qRound(dpmy * 0.0254);
1659 case PdmDevicePixelRatio:
1660 return QWidget::metric(metric);
1661 case PdmDevicePixelRatioScaled:
1662 return QWidget::metric(metric);
1663 case PdmDevicePixelRatioF_EncodedA:
1664 Q_FALLTHROUGH();
1665 case PdmDevicePixelRatioF_EncodedB:
1666 return QWidget::metric(metric);
1667 default:
1668 qWarning("QOpenGLWidget::metric(): unknown metric %d", metric);
1669 return 0;
1670 }
1671}
1672
1673/*!
1674 \reimp
1675*/
1676QPaintDevice *QOpenGLWidget::redirected(QPoint *p) const
1677{
1678 Q_D(const QOpenGLWidget);
1679 if (d->inBackingStorePaint)
1680 return QWidget::redirected(p);
1681
1682 return d->paintDevice;
1683}
1684
1685/*!
1686 \reimp
1687*/
1688QPaintEngine *QOpenGLWidget::paintEngine() const
1689{
1690 Q_D(const QOpenGLWidget);
1691 // QWidget needs to "punch a hole" into the backingstore. This needs the
1692 // normal paint engine and device, not the GL one. So in this mode, behave
1693 // like a normal widget.
1694 if (d->inBackingStorePaint)
1695 return QWidget::paintEngine();
1696
1697 if (!d->initialized)
1698 return nullptr;
1699
1700 return d->paintDevice->paintEngine();
1701}
1702
1703
1704bool QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
1705{
1706 Q_Q(QOpenGLWidget);
1707
1708 if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
1709 return false;
1710
1711 currentTargetBuffer = targetBuffer;
1712 q->makeCurrent();
1713
1714 return true;
1715}
1716
1717/*!
1718 \reimp
1719*/
1720bool QOpenGLWidget::event(QEvent *e)
1721{
1722 Q_D(QOpenGLWidget);
1723 switch (e->type()) {
1724 case QEvent::WindowAboutToChangeInternal:
1725 d->resetRhiDependentResources();
1726 break;
1727 case QEvent::WindowChangeInternal:
1728 if (QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
1729 break;
1730 if (d->initialized)
1731 d->reset();
1732 if (isHidden())
1733 break;
1734 Q_FALLTHROUGH();
1735 case QEvent::Show: // reparenting may not lead to a resize so reinitialize on Show too
1736 if (d->initialized && !d->wrapperTextures[d->currentTargetBuffer] && window()->windowHandle()) {
1737 // Special case: did grabFramebuffer() for a hidden widget that then became visible.
1738 // Recreate all resources since the context now needs to share with the TLW's.
1739 if (!QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
1740 d->reset();
1741 }
1742 if (d->rhi()) {
1743 if (!d->initialized && !size().isEmpty()) {
1744 d->initialize();
1745 if (d->initialized) {
1746 d->recreateFbos();
1747 // QTBUG-89812: generate a paint event, like resize would do,
1748 // otherwise a QOpenGLWidget in a QDockWidget may not show the
1749 // content upon (un)docking.
1750 d->sendPaintEvent(QRect(QPoint(0, 0), size()));
1751 }
1752 }
1753 }
1754 break;
1755 case QEvent::DevicePixelRatioChange:
1756 if (d->initialized && d->paintDevice->devicePixelRatio() != devicePixelRatio())
1757 d->recreateFbos();
1758 break;
1759 default:
1760 break;
1761 }
1762 return QWidget::event(e);
1763}
1764
1765QT_END_NAMESPACE
1766
1767#include "moc_qopenglwidget.cpp"
QOpenGLWidgetPaintDevicePrivate(QOpenGLWidget *widget)
QOpenGLWidgetPaintDevice(QOpenGLWidget *widget)
void ensureActiveTarget() override
This virtual method is provided as a callback to allow re-binding a target frame buffer object or con...
void endBackingStorePainting() override
void beginCompose() override
QOpenGLFramebufferObject * resolvedFbos[2]
QPlatformTextureList::Flags textureListFlags() override
bool setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
static constexpr GLenum gl_color_attachment0
static constexpr GLenum gl_depth_attachment
void initializeViewportFramebuffer() override
QWidgetPrivate::TextureData texture() const override
QOpenGLContext * context
static constexpr GLenum gl_depth_stencil_attachment
static constexpr GLenum gl_stencil_attachment
void beginBackingStorePainting() override
void endCompose() override
QPlatformBackingStoreRhiConfig rhiConfig() const override
QOpenGLPaintDevice * paintDevice
QImage grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer)
QImage grabFramebuffer() override
void resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
QSurfaceFormat requestedFormat
void resolveSamples() override
bool isStereoEnabled() override
void resizeViewportFramebuffer() override
QOffscreenSurface * surface
QRhiTexture * wrapperTextures[2]
bool toggleStereoTargetBuffer() override
QOpenGLFramebufferObject * fbos[2]
Combined button and popup list for selecting options.
#define GL_SRGB_ALPHA
Definition qopenglext.h:864
#define GL_SRGB8_ALPHA8
Definition qopenglext.h:865
#define GL_SRGB
Definition qopenglext.h:862
#define GL_SRGB8
Definition qopenglext.h:863