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