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;
599};
600
602{
603 // NB! autoFillBackground is and must be false by default. Otherwise we would clear on
604 // every QPainter begin() which is not desirable. This is only for legacy use cases,
605 // like using QOpenGLWidget as the viewport of a graphics view, that expect clearing
606 // with the palette's background color.
607 if (w->autoFillBackground()) {
608 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
609 if (w->format().hasAlpha()) {
610 f->glClearColor(0, 0, 0, 0);
611 } else {
612 QColor c = w->palette().brush(w->backgroundRole()).color();
613 float alpha = c.alphaF();
614 f->glClearColor(c.redF() * alpha, c.greenF() * alpha, c.blueF() * alpha, alpha);
615 }
616 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
617 }
618}
619
621{
622 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(w));
623 if (!wd->initialized)
624 return;
625
626 if (!wd->inPaintGL)
627 QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = 0;
628}
629
631{
632 QOpenGLWidgetPaintDevicePrivate *d = static_cast<QOpenGLWidgetPaintDevicePrivate *>(d_ptr.data());
633 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(d->w));
634 if (!wd->initialized)
635 return;
636
637 if (QOpenGLContext::currentContext() != wd->context)
638 d->w->makeCurrent();
639 else
640 wd->fbos[wd->currentTargetBuffer]->bind();
641
642
643 if (!wd->inPaintGL)
644 QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = wd->fbos[wd->currentTargetBuffer]->handle();
645
646 // When used as a viewport, drawing is done via opening a QPainter on the widget
647 // without going through paintEvent(). We will have to make sure a glFlush() is done
648 // before the texture is accessed also in this case.
649 wd->flushPending = true;
650}
651
653{
654 return { wrapperTextures[QOpenGLWidget::LeftBuffer], wrapperTextures[QOpenGLWidget::RightBuffer] };
655}
656
657#ifndef GL_SRGB
658#define GL_SRGB 0x8C40
659#endif
660#ifndef GL_SRGB8
661#define GL_SRGB8 0x8C41
662#endif
663#ifndef GL_SRGB_ALPHA
664#define GL_SRGB_ALPHA 0x8C42
665#endif
666#ifndef GL_SRGB8_ALPHA8
667#define GL_SRGB8_ALPHA8 0x8C43
668#endif
669
671{
672 QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags();
673 switch (textureFormat) {
674 case GL_SRGB:
675 case GL_SRGB8:
676 case GL_SRGB_ALPHA:
677 case GL_SRGB8_ALPHA8:
678 flags |= QPlatformTextureList::TextureIsSrgb;
679 break;
680 default:
681 break;
682 }
683 return flags;
684}
685
687{
688 Q_Q(QOpenGLWidget);
689
690 // Destroy the OpenGL resources first. These need the context to be current.
691 if (initialized)
692 q->makeCurrent();
693
694 delete paintDevice;
695 paintDevice = nullptr;
696
698
699 if (initialized)
700 q->doneCurrent();
701
702 // Delete the context first, then the surface. Slots connected to
703 // the context's aboutToBeDestroyed() may still call makeCurrent()
704 // to perform some cleanup.
705 delete context;
706 context = nullptr;
707 delete surface;
708 surface = nullptr;
710}
711
713{
714 // QRhi resource created from the QRhi. These must be released whenever the
715 // widget gets associated with a different QRhi, even when all OpenGL
716 // contexts share resources.
717
718 delete wrapperTextures[0];
719 wrapperTextures[0] = nullptr;
720
721 if (isStereoEnabled()) {
722 delete wrapperTextures[1];
723 wrapperTextures[1] = nullptr;
724 }
725}
726
728{
729 Q_Q(QOpenGLWidget);
730
731 emit q->aboutToResize();
732
733 context->makeCurrent(surface);
734
736
737 int samples = requestedSamples;
738 QOpenGLExtensions *extfuncs = static_cast<QOpenGLExtensions *>(context->functions());
739 if (!extfuncs->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample))
740 samples = 0;
741
742 QOpenGLFramebufferObjectFormat format;
743 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
744 format.setSamples(samples);
745 if (textureFormat)
746 format.setInternalTextureFormat(textureFormat);
747
748 const QSize deviceSize = q->size() * q->devicePixelRatio();
749 fbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
750 if (samples > 0)
751 resolvedFbos[QOpenGLWidget::LeftBuffer] = new QOpenGLFramebufferObject(deviceSize);
752
753 const bool stereo = isStereoEnabled();
754
755 if (stereo) {
756 fbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize, format);
757 if (samples > 0)
758 resolvedFbos[QOpenGLWidget::RightBuffer] = new QOpenGLFramebufferObject(deviceSize);
759 }
760
761 textureFormat = fbos[QOpenGLWidget::LeftBuffer]->format().internalTextureFormat();
762
763 currentTargetBuffer = QOpenGLWidget::LeftBuffer;
764 fbos[currentTargetBuffer]->bind();
765 context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
767
768 if (stereo) {
769 currentTargetBuffer = QOpenGLWidget::RightBuffer;
770 fbos[currentTargetBuffer]->bind();
771 context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
773 currentTargetBuffer = QOpenGLWidget::LeftBuffer;
774 }
775
776 flushPending = true; // Make sure the FBO is initialized before use
777
778 paintDevice->setSize(deviceSize);
779 paintDevice->setDevicePixelRatio(q->devicePixelRatio());
780
781 emit q->resized();
782}
783
785{
786 Q_Q(QOpenGLWidget);
787
788 QRhi *rhi = QWidgetPrivate::rhi();
789
790 // If there is no rhi, because we are completely offscreen, then there's no wrapperTexture either
791 if (rhi && rhi->backend() == QRhi::OpenGLES2) {
792 const QSize deviceSize = q->size() * q->devicePixelRatio();
793 if (!wrapperTextures[currentTargetBuffer] || wrapperTextures[currentTargetBuffer]->pixelSize() != deviceSize) {
794 const uint textureId = resolvedFbos[currentTargetBuffer] ?
795 resolvedFbos[currentTargetBuffer]->texture()
796 : (fbos[currentTargetBuffer] ? fbos[currentTargetBuffer]->texture() : 0);
797 if (!wrapperTextures[currentTargetBuffer])
798 wrapperTextures[currentTargetBuffer] = rhi->newTexture(QRhiTexture::RGBA8, deviceSize, 1, QRhiTexture::RenderTarget);
799 else
800 wrapperTextures[currentTargetBuffer]->setPixelSize(deviceSize);
801 if (!wrapperTextures[currentTargetBuffer]->createFrom({textureId, 0 }))
802 qWarning("QOpenGLWidget: Failed to create wrapper texture");
803 }
804 }
805}
806
808{
809 Q_Q(QOpenGLWidget);
810 if (flushPending) {
811 flushPending = false;
812 q->makeCurrent();
813 static_cast<QOpenGLExtensions *>(context->functions())->flushShared();
814 }
815 hasBeenComposed = true;
816 emit q->aboutToCompose();
817}
818
820{
821 Q_Q(QOpenGLWidget);
822 emit q->frameSwapped();
823}
824
826{
827 Q_Q(QOpenGLWidget);
828 if (initialized)
829 return;
830
831 // If no global shared context get our toplevel's context with which we
832 // will share in order to make the texture usable by the underlying window's backingstore.
833 QWidget *tlw = q->window();
834
835 // Do not include the sample count. Requesting a multisampled context is not necessary
836 // since we render into an FBO, never to an actual surface. What's more, attempting to
837 // create a pbuffer with a multisampled config crashes certain implementations. Just
838 // avoid the entire hassle, the result is the same.
839 requestedSamples = requestedFormat.samples();
840 requestedFormat.setSamples(0);
841
842 QRhi *rhi = QWidgetPrivate::rhi();
843
844 // Could be that something else already initialized the window with some
845 // other graphics API for the QRhi, that's not good.
846 if (rhi && rhi->backend() != QRhi::OpenGLES2) {
847 qWarning("The top-level window is not using OpenGL for composition, '%s' is not compatible with QOpenGLWidget",
848 rhi->backendName());
849 return;
850 }
851
852 // If rhi or contextFromRhi is null, showing content on-screen will not work.
853 // However, offscreen rendering and grabFramebuffer() will stay fully functional.
854
855 QOpenGLContext *contextFromRhi = rhi ? static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles())->context : nullptr;
856
857 context = new QOpenGLContext;
858 context->setFormat(requestedFormat);
859
860 QOpenGLContext *shareContext = contextFromRhi ? contextFromRhi : QOpenGLContext::globalShareContext();
861 if (shareContext) {
862 context->setShareContext(shareContext);
863 context->setScreen(shareContext->screen());
864 }
865 if (Q_UNLIKELY(!context->create())) {
866 qWarning("QOpenGLWidget: Failed to create context");
867 return;
868 }
869
870 surface = new QOffscreenSurface;
871 surface->setFormat(context->format());
872 surface->setScreen(context->screen());
873 surface->create();
874
875 if (Q_UNLIKELY(!context->makeCurrent(surface))) {
876 qWarning("QOpenGLWidget: Failed to make context current");
877 return;
878 }
879
880 // Propagate settings that make sense only for the tlw. Note that this only
881 // makes sense for properties that get picked up even after the native
882 // window is created.
883 if (tlw->windowHandle()) {
884 QSurfaceFormat tlwFormat = tlw->windowHandle()->format();
885 if (requestedFormat.swapInterval() != tlwFormat.swapInterval()) {
886 // Most platforms will pick up the changed swap interval on the next
887 // makeCurrent or swapBuffers.
888 tlwFormat.setSwapInterval(requestedFormat.swapInterval());
889 tlw->windowHandle()->setFormat(tlwFormat);
890 }
891 if (requestedFormat.swapBehavior() != tlwFormat.swapBehavior()) {
892 tlwFormat.setSwapBehavior(requestedFormat.swapBehavior());
893 tlw->windowHandle()->setFormat(tlwFormat);
894 }
895 }
896
897 paintDevice = new QOpenGLWidgetPaintDevice(q);
898 paintDevice->setSize(q->size() * q->devicePixelRatio());
899 paintDevice->setDevicePixelRatio(q->devicePixelRatio());
900
901 initialized = true;
902
903 q->initializeGL();
904}
905
907{
908 resolveSamplesForBuffer(QOpenGLWidget::LeftBuffer);
909 resolveSamplesForBuffer(QOpenGLWidget::RightBuffer);
910}
911
912void QOpenGLWidgetPrivate::resolveSamplesForBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
913{
914 Q_Q(QOpenGLWidget);
915 if (resolvedFbos[targetBuffer]) {
916 q->makeCurrent(targetBuffer);
917 QRect rect(QPoint(0, 0), fbos[targetBuffer]->size());
918 QOpenGLFramebufferObject::blitFramebuffer(resolvedFbos[targetBuffer], rect, fbos[targetBuffer], rect);
919 flushPending = true;
920 }
921}
922
924{
925 Q_Q(QOpenGLWidget);
926
927 if (fakeHidden || !initialized)
928 return;
929
930 setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
931
932 QOpenGLContext *ctx = QOpenGLContext::currentContext();
933 if (!ctx) {
934 qWarning("QOpenGLWidget: No current context, cannot render");
935 return;
936 }
937
938 if (!fbos[QOpenGLWidget::LeftBuffer]) {
939 qWarning("QOpenGLWidget: No fbo, cannot render");
940 return;
941 }
942
943 const bool stereo = isStereoEnabled();
944 if (stereo) {
945 static bool warningGiven = false;
946 if (!fbos[QOpenGLWidget::RightBuffer] && !warningGiven) {
947 qWarning("QOpenGLWidget: Stereo is enabled, but no right buffer. Using only left buffer");
948 warningGiven = true;
949 }
950 }
951
952 if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
954
955 if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
956 setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
958 setCurrentTargetBuffer(QOpenGLWidget::LeftBuffer);
959 }
960
961 hasBeenComposed = false;
962 }
963
964 QOpenGLFunctions *f = ctx->functions();
965 f->glViewport(0, 0, q->width() * q->devicePixelRatio(), q->height() * q->devicePixelRatio());
966 inPaintGL = true;
967
968#ifdef Q_OS_WASM
969 f->glDepthMask(GL_TRUE);
970#endif
971
972 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
973
974 f->glUseProgram(0);
975 f->glBindBuffer(GL_ARRAY_BUFFER, 0);
976 f->glEnable(GL_BLEND);
977
978 q->paintGL();
979 if (updateBehavior == QOpenGLWidget::NoPartialUpdate)
981
982 if (stereo && fbos[QOpenGLWidget::RightBuffer]) {
983 setCurrentTargetBuffer(QOpenGLWidget::RightBuffer);
984 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbos[currentTargetBuffer]->handle();
985 q->paintGL();
986 if (updateBehavior == QOpenGLWidget::NoPartialUpdate)
988 }
989 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
990
991 inPaintGL = false;
992 flushPending = true;
993}
994
996{
997 QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
998 if (f->hasOpenGLExtension(QOpenGLExtensions::DiscardFramebuffer)) {
999 const GLenum attachments[] = {
1000 gl_color_attachment0,
1001 gl_depth_attachment,
1002 gl_stencil_attachment,
1003#ifdef Q_OS_WASM
1004 // webgl does not allow separate depth and stencil attachments
1005 // QTBUG-69913
1006 gl_depth_stencil_attachment
1007#endif
1008 };
1009 f->discardFramebuffer(GL_FRAMEBUFFER, GLsizei(std::size(attachments)), attachments);
1010 } else {
1011 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
1012 }
1013}
1014
1016{
1017 QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
1018 if (f->hasOpenGLExtension(QOpenGLExtensions::DiscardFramebuffer)) {
1019 const GLenum attachments[] = {
1020 gl_depth_attachment,
1021 gl_stencil_attachment,
1022#ifdef Q_OS_WASM
1023 // webgl does not allow separate depth and stencil attachments
1024 // QTBUG-69913
1025 gl_depth_stencil_attachment
1026#endif
1027 };
1028 f->discardFramebuffer(GL_FRAMEBUFFER, GLsizei(std::size(attachments)), attachments);
1029 }
1030}
1031
1033{
1034 delete fbos[QOpenGLWidget::LeftBuffer];
1035 fbos[QOpenGLWidget::LeftBuffer] = nullptr;
1036 delete resolvedFbos[QOpenGLWidget::LeftBuffer];
1037 resolvedFbos[QOpenGLWidget::LeftBuffer] = nullptr;
1038
1039 delete fbos[QOpenGLWidget::RightBuffer];
1040 fbos[QOpenGLWidget::RightBuffer] = nullptr;
1041 delete resolvedFbos[QOpenGLWidget::RightBuffer];
1042 resolvedFbos[QOpenGLWidget::RightBuffer] = nullptr;
1043
1045}
1046
1048{
1049 return grabFramebuffer(QOpenGLWidget::LeftBuffer);
1050}
1051
1052QImage QOpenGLWidgetPrivate::grabFramebuffer(QOpenGLWidget::TargetBuffer targetBuffer)
1053{
1054 Q_Q(QOpenGLWidget);
1055
1057 if (!initialized)
1058 return QImage();
1059
1060 // The second fbo is only created when stereoscopic rendering is enabled
1061 // Just use the default one if not.
1062 if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
1063 targetBuffer = QOpenGLWidget::LeftBuffer;
1064
1065 if (!fbos[targetBuffer]) // could be completely offscreen, without ever getting a resize event
1067
1068 if (!inPaintGL)
1069 render();
1070
1071 setCurrentTargetBuffer(targetBuffer);
1072 if (resolvedFbos[targetBuffer]) {
1073 resolveSamplesForBuffer(targetBuffer);
1074 resolvedFbos[targetBuffer]->bind();
1075 }
1076
1077 const bool hasAlpha = q->format().hasAlpha();
1078 QImage res = qt_gl_read_framebuffer(q->size() * q->devicePixelRatio(), hasAlpha, hasAlpha);
1079 res.setDevicePixelRatio(q->devicePixelRatio());
1080
1081 // While we give no guarantees of what is going to be left bound, prefer the
1082 // multisample fbo instead of the resolved one. Clients may continue to
1083 // render straight after calling this function.
1084 if (resolvedFbos[targetBuffer]) {
1085 setCurrentTargetBuffer(targetBuffer);
1086 }
1087
1088 return res;
1089}
1090
1092{
1093 Q_Q(QOpenGLWidget);
1094 // Legacy behavior for compatibility with QGLWidget when used as a graphics view
1095 // viewport: enable clearing on each painter begin.
1096 q->setAutoFillBackground(true);
1097}
1098
1100{
1101 Q_Q(QOpenGLWidget);
1102 // Note that because this internally might use the requested format,
1103 // then this can return a false positive on hardware where
1104 // steroscopic rendering is not supported.
1105 return q->format().stereo();
1106}
1107
1109{
1110 return setCurrentTargetBuffer(currentTargetBuffer == QOpenGLWidget::LeftBuffer ?
1111 QOpenGLWidget::RightBuffer :
1112 QOpenGLWidget::LeftBuffer);
1113}
1114
1116{
1117 Q_Q(QOpenGLWidget);
1118 if (!initialized)
1119 return;
1120
1121 if (!fbos[currentTargetBuffer] || q->size() * q->devicePixelRatio() != fbos[currentTargetBuffer]->size()) {
1123 q->update();
1124 }
1125}
1126
1127/*!
1128 Constructs a widget which is a child of \a parent, with widget flags set to \a f.
1129 */
1130QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f)
1131 : QWidget(*(new QOpenGLWidgetPrivate), parent, f)
1132{
1133 Q_D(QOpenGLWidget);
1134 if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)
1135 || !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)))
1136 qWarning("QOpenGLWidget is not supported on this platform.");
1137 else
1138 d->setRenderToTexture();
1139}
1140
1141/*!
1142 Destroys the QOpenGLWidget instance, freeing its resources.
1143
1144 The QOpenGLWidget's context is made current in the destructor, allowing for
1145 safe destruction of any child object that may need to release OpenGL
1146 resources belonging to the context provided by this widget.
1147
1148 \warning if you have objects wrapping OpenGL resources (such as
1149 QOpenGLBuffer, QOpenGLShaderProgram, etc.) as members of a OpenGLWidget
1150 subclass, you may need to add a call to makeCurrent() in that subclass'
1151 destructor as well. Due to the rules of C++ object destruction, those objects
1152 will be destroyed \e{before} calling this function (but after that the
1153 destructor of the subclass has run), therefore making the OpenGL context
1154 current in this function happens too late for their safe disposal.
1155
1156 \sa makeCurrent
1157*/
1158QOpenGLWidget::~QOpenGLWidget()
1159{
1160 // NB! resetting graphics resources must be done from this destructor,
1161 // *not* from the private class' destructor. This is due to how destruction
1162 // works and due to the QWidget dtor (for toplevels) destroying the repaint
1163 // manager and rhi before the (QObject) private gets destroyed. Hence must
1164 // do it here early on.
1165
1166 Q_D(QOpenGLWidget);
1167 d->reset();
1168}
1169
1170/*!
1171 Sets this widget's update behavior to \a updateBehavior.
1172 \since 5.5
1173*/
1174void QOpenGLWidget::setUpdateBehavior(UpdateBehavior updateBehavior)
1175{
1176 Q_D(QOpenGLWidget);
1177 d->updateBehavior = updateBehavior;
1178}
1179
1180/*!
1181 \return the update behavior of the widget.
1182 \since 5.5
1183*/
1184QOpenGLWidget::UpdateBehavior QOpenGLWidget::updateBehavior() const
1185{
1186 Q_D(const QOpenGLWidget);
1187 return d->updateBehavior;
1188}
1189
1190/*!
1191 Sets the requested surface \a format.
1192
1193 When the format is not explicitly set via this function, the format returned by
1194 QSurfaceFormat::defaultFormat() will be used. This means that when having multiple
1195 OpenGL widgets, individual calls to this function can be replaced by one single call to
1196 QSurfaceFormat::setDefaultFormat() before creating the first widget.
1197
1198 \note Requesting an alpha buffer via this function will not lead to the
1199 desired results when the intention is to make other widgets beneath visible.
1200 Instead, use Qt::WA_AlwaysStackOnTop to enable semi-transparent QOpenGLWidget
1201 instances with other widgets visible underneath. Keep in mind however that
1202 this breaks the stacking order, so it will no longer be possible to have
1203 other widgets on top of the QOpenGLWidget.
1204
1205 \sa format(), Qt::WA_AlwaysStackOnTop, QSurfaceFormat::setDefaultFormat()
1206 */
1207void QOpenGLWidget::setFormat(const QSurfaceFormat &format)
1208{
1209 Q_D(QOpenGLWidget);
1210 if (Q_UNLIKELY(d->initialized)) {
1211 qWarning("QOpenGLWidget: Already initialized, setting the format has no effect");
1212 return;
1213 }
1214
1215 d->requestedFormat = format;
1216}
1217
1218/*!
1219 Returns the context and surface format used by this widget and its toplevel
1220 window.
1221
1222 After the widget and its toplevel have both been created, resized and shown,
1223 this function will return the actual format of the context. This may differ
1224 from the requested format if the request could not be fulfilled by the
1225 platform. It is also possible to get larger color buffer sizes than
1226 requested.
1227
1228 When the widget's window and the related OpenGL resources are not yet
1229 initialized, the return value is the format that has been set via
1230 setFormat().
1231
1232 \sa setFormat(), context()
1233 */
1234QSurfaceFormat QOpenGLWidget::format() const
1235{
1236 Q_D(const QOpenGLWidget);
1237 return d->initialized ? d->context->format() : d->requestedFormat;
1238}
1239
1240/*!
1241 Sets a custom internal texture format of \a texFormat.
1242
1243 When working with sRGB framebuffers, it will be necessary to specify a
1244 format like \c{GL_SRGB8_ALPHA8}. This can be achieved by calling this
1245 function.
1246
1247 \note This function has no effect if called after the widget has already
1248 been shown and thus it performed initialization.
1249
1250 \note This function will typically have to be used in combination with a
1251 QSurfaceFormat::setColorSpace() call that sets the color space to
1252 QColorSpace::SRgb.
1253
1254 \since 5.10
1255 */
1256void QOpenGLWidget::setTextureFormat(GLenum texFormat)
1257{
1258 Q_D(QOpenGLWidget);
1259 if (Q_UNLIKELY(d->initialized)) {
1260 qWarning("QOpenGLWidget: Already initialized, setting the internal texture format has no effect");
1261 return;
1262 }
1263
1264 d->textureFormat = texFormat;
1265}
1266
1267/*!
1268 \return the active internal texture format if the widget has already
1269 initialized, the requested format if one was set but the widget has not yet
1270 been made visible, or \nullptr if setTextureFormat() was not called and the
1271 widget has not yet been made visible.
1272
1273 \since 5.10
1274 */
1275GLenum QOpenGLWidget::textureFormat() const
1276{
1277 Q_D(const QOpenGLWidget);
1278 return d->textureFormat;
1279}
1280
1281/*!
1282 \return \e true if the widget and OpenGL resources, like the context, have
1283 been successfully initialized. Note that the return value is always false
1284 until the widget is shown.
1285*/
1286bool QOpenGLWidget::isValid() const
1287{
1288 Q_D(const QOpenGLWidget);
1289 return d->initialized && d->context->isValid();
1290}
1291
1292/*!
1293 Prepares for rendering OpenGL content for this widget by making the
1294 corresponding context current and binding the framebuffer object in that
1295 context.
1296
1297 It is not necessary to call this function in most cases, because it
1298 is called automatically before invoking paintGL().
1299
1300 \sa context(), paintGL(), doneCurrent()
1301 */
1302void QOpenGLWidget::makeCurrent()
1303{
1304 Q_D(QOpenGLWidget);
1305 if (!d->initialized)
1306 return;
1307
1308 d->context->makeCurrent(d->surface);
1309
1310 if (d->fbos[d->currentTargetBuffer]) // there may not be one if we are in reset()
1311 d->fbos[d->currentTargetBuffer]->bind();
1312}
1313
1314/*!
1315 Prepares for rendering OpenGL content for this widget by making the
1316 context for the passed in buffer current and binding the framebuffer object in that
1317 context.
1318
1319 \note This only makes sense to call when stereoscopic rendering is enabled.
1320 Nothing will happen if the right buffer is requested when it's disabled.
1321
1322 It is not necessary to call this function in most cases, because it
1323 is called automatically before invoking paintGL().
1324
1325 \since 6.5
1326
1327 \sa context(), paintGL(), doneCurrent()
1328 */
1329void QOpenGLWidget::makeCurrent(TargetBuffer targetBuffer)
1330{
1331 Q_D(QOpenGLWidget);
1332 if (!d->initialized)
1333 return;
1334
1335 // The FBO for the right buffer is only initialized when stereo is set
1336 if (targetBuffer == TargetBuffer::RightBuffer && !format().stereo())
1337 return;
1338
1339 d->setCurrentTargetBuffer(targetBuffer); // calls makeCurrent
1340}
1341
1342/*!
1343 Releases the context.
1344
1345 It is not necessary to call this function in most cases, since the
1346 widget will make sure the context is bound and released properly
1347 when invoking paintGL().
1348 */
1349void QOpenGLWidget::doneCurrent()
1350{
1351 Q_D(QOpenGLWidget);
1352 if (!d->initialized)
1353 return;
1354
1355 d->context->doneCurrent();
1356}
1357
1358/*!
1359 \return The QOpenGLContext used by this widget or \c 0 if not yet initialized.
1360
1361 \note The context and the framebuffer object used by the widget changes when
1362 reparenting the widget via setParent().
1363
1364 \sa QOpenGLContext::setShareContext(), defaultFramebufferObject()
1365 */
1366QOpenGLContext *QOpenGLWidget::context() const
1367{
1368 Q_D(const QOpenGLWidget);
1369 return d->context;
1370}
1371
1372/*!
1373 \return The framebuffer object handle or \c 0 if not yet initialized.
1374
1375 \note The framebuffer object belongs to the context returned by context()
1376 and may not be accessible from other contexts.
1377
1378 \note The context and the framebuffer object used by the widget changes when
1379 reparenting the widget via setParent(). In addition, the framebuffer object
1380 changes on each resize.
1381
1382 \sa context()
1383 */
1384GLuint QOpenGLWidget::defaultFramebufferObject() const
1385{
1386 Q_D(const QOpenGLWidget);
1387 return d->fbos[TargetBuffer::LeftBuffer] ? d->fbos[TargetBuffer::LeftBuffer]->handle() : 0;
1388}
1389
1390/*!
1391 \return The framebuffer object handle of the specified target buffer or
1392 \c 0 if not yet initialized.
1393
1394 Calling this overload only makes sense if \l QSurfaceFormat::StereoBuffers is enabled
1395 and supported by the hardware. If not, this method will return the default buffer.
1396
1397 \note The framebuffer object belongs to the context returned by context()
1398 and may not be accessible from other contexts. The context and the framebuffer
1399 object used by the widget changes when reparenting the widget via setParent().
1400 In addition, the framebuffer object changes on each resize.
1401
1402 \since 6.5
1403
1404 \sa context()
1405 */
1406GLuint QOpenGLWidget::defaultFramebufferObject(TargetBuffer targetBuffer) const
1407{
1408 Q_D(const QOpenGLWidget);
1409 return d->fbos[targetBuffer] ? d->fbos[targetBuffer]->handle() : 0;
1410}
1411
1412/*!
1413 This virtual function is called once before the first call to
1414 paintGL() or resizeGL(). Reimplement it in a subclass.
1415
1416 This function should set up any required OpenGL resources.
1417
1418 There is no need to call makeCurrent() because this has already been
1419 done when this function is called. Note however that the framebuffer
1420 is not yet available at this stage, so avoid issuing draw calls from
1421 here. Defer such calls to paintGL() instead.
1422
1423 \sa paintGL(), resizeGL()
1424*/
1425void QOpenGLWidget::initializeGL()
1426{
1427}
1428
1429/*!
1430 This virtual function is called whenever the widget has been
1431 resized. Reimplement it in a subclass. The new size is passed in
1432 \a w and \a h.
1433
1434 There is no need to call makeCurrent() because this has already been
1435 done when this function is called. Additionally, the framebuffer is
1436 also bound.
1437
1438 \sa initializeGL(), paintGL()
1439*/
1440void QOpenGLWidget::resizeGL(int w, int h)
1441{
1442 Q_UNUSED(w);
1443 Q_UNUSED(h);
1444}
1445
1446/*!
1447 This virtual function is called whenever the widget needs to be
1448 painted. Reimplement it in a subclass.
1449
1450 There is no need to call makeCurrent() because this has already
1451 been done when this function is called.
1452
1453 Before invoking this function, the context and the framebuffer are
1454 bound, and the viewport is set up by a call to glViewport(). No
1455 other state is set and no clearing or drawing is performed by the
1456 framework.
1457
1458 The default implementation performs a glClear(). Subclasses are not expected
1459 to invoke the base class implementation and should perform clearing on their
1460 own.
1461
1462 \note To ensure portability, do not expect that state set in initializeGL()
1463 persists. Rather, set all necessary state, for example, by calling
1464 glEnable(), in paintGL(). This is because some platforms, such as WebAssembly
1465 with WebGL, may have limitations on OpenGL contexts in some situations, which
1466 can lead to using the context used with the QOpenGLWidget for other purposes
1467 as well.
1468
1469 When \l QSurfaceFormat::StereoBuffers is enabled, this function
1470 will be called twice - once for each buffer. Query what buffer is
1471 currently bound by calling currentTargetBuffer().
1472
1473 \note The framebuffer of each target will be drawn to even when
1474 stereoscopic rendering is not supported by the hardware.
1475 Only the left buffer will actually be visible in the window.
1476
1477 \sa initializeGL(), resizeGL(), currentTargetBuffer()
1478*/
1479void QOpenGLWidget::paintGL()
1480{
1481 Q_D(QOpenGLWidget);
1482 if (d->initialized)
1483 d->context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
1484}
1485
1486/*!
1487 Handles resize events that are passed in the \a e event parameter.
1488 Calls the virtual function resizeGL().
1489
1490 \note Avoid overriding this function in derived classes. If that is not
1491 feasible, make sure that QOpenGLWidget's implementation is invoked
1492 too. Otherwise the underlying framebuffer object and related resources will
1493 not get resized properly and will lead to incorrect rendering.
1494*/
1495void QOpenGLWidget::resizeEvent(QResizeEvent *e)
1496{
1497 Q_D(QOpenGLWidget);
1498
1499 if (e->size().isEmpty()) {
1500 d->fakeHidden = true;
1501 return;
1502 }
1503 d->fakeHidden = false;
1504
1505 d->initialize();
1506 if (!d->initialized)
1507 return;
1508
1509 d->recreateFbos();
1510 // Make sure our own context is current before invoking user overrides. If
1511 // the fbo was recreated then there's a chance something else is current now.
1512 makeCurrent();
1513 resizeGL(width(), height());
1514 d->sendPaintEvent(QRect(QPoint(0, 0), size()));
1515}
1516
1517/*!
1518 Handles paint events.
1519
1520 Calling QWidget::update() will lead to sending a paint event \a e,
1521 and thus invoking this function. (NB this is asynchronous and will
1522 happen at some point after returning from update()). This function
1523 will then, after some preparation, call the virtual paintGL() to
1524 update the contents of the QOpenGLWidget's framebuffer. The widget's
1525 top-level window will then composite the framebuffer's texture with
1526 the rest of the window.
1527*/
1528void QOpenGLWidget::paintEvent(QPaintEvent *e)
1529{
1530 Q_UNUSED(e);
1531 Q_D(QOpenGLWidget);
1532
1533 d->initialize();
1534 if (d->initialized) {
1535 d->ensureRhiDependentResources();
1536 if (updatesEnabled())
1537 d->render();
1538 }
1539}
1540
1541/*!
1542 Renders and returns a 32-bit RGB image of the framebuffer.
1543
1544 \note This is a potentially expensive operation because it relies on glReadPixels()
1545 to read back the pixels. This may be slow and can stall the GPU pipeline.
1546*/
1547QImage QOpenGLWidget::grabFramebuffer()
1548{
1549 Q_D(QOpenGLWidget);
1550 return d->grabFramebuffer();
1551}
1552
1553/*!
1554 Renders and returns a 32-bit RGB image of the framebuffer of the specified target buffer.
1555 This overload only makes sense to call when \l QSurfaceFormat::StereoBuffers is enabled.
1556 Grabbing the framebuffer of the right target buffer will return the default image
1557 if stereoscopic rendering is disabled or if not supported by the hardware.
1558
1559 \note This is a potentially expensive operation because it relies on glReadPixels()
1560 to read back the pixels. This may be slow and can stall the GPU pipeline.
1561
1562 \since 6.5
1563*/
1564QImage QOpenGLWidget::grabFramebuffer(TargetBuffer targetBuffer)
1565{
1566 Q_D(QOpenGLWidget);
1567 return d->grabFramebuffer(targetBuffer);
1568}
1569
1570/*!
1571 Returns the currently active target buffer. This will be the left buffer by default,
1572 the right buffer is only used when \l QSurfaceFormat::StereoBuffers is enabled.
1573 When stereoscopic rendering is enabled, this can be queried in paintGL() to know
1574 what buffer is currently in use. paintGL() will be called twice, once for each target.
1575
1576 \since 6.5
1577
1578 \sa paintGL()
1579*/
1580QOpenGLWidget::TargetBuffer QOpenGLWidget::currentTargetBuffer() const
1581{
1582 Q_D(const QOpenGLWidget);
1583 return d->currentTargetBuffer;
1584}
1585
1586/*!
1587 \reimp
1588*/
1589int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
1590{
1591 Q_D(const QOpenGLWidget);
1592 if (d->inBackingStorePaint)
1593 return QWidget::metric(metric);
1594
1595 auto window = d->windowHandle(QWidgetPrivate::WindowHandleMode::TopLevel);
1596 QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
1597
1598 const float dpmx = qt_defaultDpiX() * 100. / 2.54;
1599 const float dpmy = qt_defaultDpiY() * 100. / 2.54;
1600
1601 switch (metric) {
1602 case PdmWidth:
1603 return width();
1604 case PdmHeight:
1605 return height();
1606 case PdmDepth:
1607 return 32;
1608 case PdmWidthMM:
1609 if (screen)
1610 return width() * screen->physicalSize().width() / screen->geometry().width();
1611 else
1612 return width() * 1000 / dpmx;
1613 case PdmHeightMM:
1614 if (screen)
1615 return height() * screen->physicalSize().height() / screen->geometry().height();
1616 else
1617 return height() * 1000 / dpmy;
1618 case PdmNumColors:
1619 return 0;
1620 case PdmDpiX:
1621 if (screen)
1622 return qRound(screen->logicalDotsPerInchX());
1623 else
1624 return qRound(dpmx * 0.0254);
1625 case PdmDpiY:
1626 if (screen)
1627 return qRound(screen->logicalDotsPerInchY());
1628 else
1629 return qRound(dpmy * 0.0254);
1630 case PdmPhysicalDpiX:
1631 if (screen)
1632 return qRound(screen->physicalDotsPerInchX());
1633 else
1634 return qRound(dpmx * 0.0254);
1635 case PdmPhysicalDpiY:
1636 if (screen)
1637 return qRound(screen->physicalDotsPerInchY());
1638 else
1639 return qRound(dpmy * 0.0254);
1640 case PdmDevicePixelRatio:
1641 return QWidget::metric(metric);
1642 case PdmDevicePixelRatioScaled:
1643 return QWidget::metric(metric);
1644 case PdmDevicePixelRatioF_EncodedA:
1645 Q_FALLTHROUGH();
1646 case PdmDevicePixelRatioF_EncodedB:
1647 return QWidget::metric(metric);
1648 default:
1649 qWarning("QOpenGLWidget::metric(): unknown metric %d", metric);
1650 return 0;
1651 }
1652}
1653
1654/*!
1655 \reimp
1656*/
1657QPaintDevice *QOpenGLWidget::redirected(QPoint *p) const
1658{
1659 Q_D(const QOpenGLWidget);
1660 if (d->inBackingStorePaint)
1661 return QWidget::redirected(p);
1662
1663 return d->paintDevice;
1664}
1665
1666/*!
1667 \reimp
1668*/
1669QPaintEngine *QOpenGLWidget::paintEngine() const
1670{
1671 Q_D(const QOpenGLWidget);
1672 // QWidget needs to "punch a hole" into the backingstore. This needs the
1673 // normal paint engine and device, not the GL one. So in this mode, behave
1674 // like a normal widget.
1675 if (d->inBackingStorePaint)
1676 return QWidget::paintEngine();
1677
1678 if (!d->initialized)
1679 return nullptr;
1680
1681 return d->paintDevice->paintEngine();
1682}
1683
1684
1685bool QOpenGLWidgetPrivate::setCurrentTargetBuffer(QOpenGLWidget::TargetBuffer targetBuffer)
1686{
1687 Q_Q(QOpenGLWidget);
1688
1689 if (targetBuffer == QOpenGLWidget::RightBuffer && !isStereoEnabled())
1690 return false;
1691
1692 currentTargetBuffer = targetBuffer;
1693 q->makeCurrent();
1694
1695 return true;
1696}
1697
1698/*!
1699 \reimp
1700*/
1701bool QOpenGLWidget::event(QEvent *e)
1702{
1703 Q_D(QOpenGLWidget);
1704 switch (e->type()) {
1705 case QEvent::WindowAboutToChangeInternal:
1706 d->resetRhiDependentResources();
1707 break;
1708 case QEvent::WindowChangeInternal:
1709 if (QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
1710 break;
1711 if (d->initialized)
1712 d->reset();
1713 if (isHidden())
1714 break;
1715 Q_FALLTHROUGH();
1716 case QEvent::Show: // reparenting may not lead to a resize so reinitialize on Show too
1717 if (d->initialized && !d->wrapperTextures[d->currentTargetBuffer] && window()->windowHandle()) {
1718 // Special case: did grabFramebuffer() for a hidden widget that then became visible.
1719 // Recreate all resources since the context now needs to share with the TLW's.
1720 if (!QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
1721 d->reset();
1722 }
1723 if (d->rhi()) {
1724 if (!d->initialized && !size().isEmpty()) {
1725 d->initialize();
1726 if (d->initialized) {
1727 d->recreateFbos();
1728 // QTBUG-89812: generate a paint event, like resize would do,
1729 // otherwise a QOpenGLWidget in a QDockWidget may not show the
1730 // content upon (un)docking.
1731 d->sendPaintEvent(QRect(QPoint(0, 0), size()));
1732 }
1733 }
1734 }
1735 break;
1736 case QEvent::DevicePixelRatioChange:
1737 if (d->initialized && d->paintDevice->devicePixelRatio() != devicePixelRatio())
1738 d->recreateFbos();
1739 break;
1740 default:
1741 break;
1742 }
1743 return QWidget::event(e);
1744}
1745
1746QT_END_NAMESPACE
1747
1748#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