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

source code of qtbase/src/openglwidgets/qopenglwidget.cpp