1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qopenglwidget.h"
41#include <QtGui/QOpenGLContext>
42#include <QtGui/QOpenGLFramebufferObject>
43#include <QtGui/QOffscreenSurface>
44#include <QtGui/QOpenGLFunctions>
45#include <QtGui/QWindow>
46#include <QtGui/QGuiApplication>
47#include <QtGui/QScreen>
48#include <QtGui/QOpenGLPaintDevice>
49#include <QtGui/qpa/qplatformwindow.h>
50#include <QtGui/qpa/qplatformintegration.h>
51#include <QtGui/private/qguiapplication_p.h>
52#include <QtGui/private/qopenglextensions_p.h>
53#include <QtGui/private/qfont_p.h>
54#include <QtGui/private/qopenglpaintdevice_p.h>
55#include <QtGui/private/qopenglcontext_p.h>
56#include <QtWidgets/private/qwidget_p.h>
57
58QT_BEGIN_NAMESPACE
59
60/*!
61 \class QOpenGLWidget
62 \inmodule QtWidgets
63 \since 5.4
64
65 \brief The QOpenGLWidget class is a widget for rendering OpenGL graphics.
66
67 QOpenGLWidget provides functionality for displaying OpenGL graphics
68 integrated into a Qt application. It is very simple to use: Make
69 your class inherit from it and use the subclass like any other
70 QWidget, except that you have the choice between using QPainter and
71 standard OpenGL rendering commands.
72
73 QOpenGLWidget provides three convenient virtual functions that you
74 can reimplement in your subclass to perform the typical OpenGL
75 tasks:
76
77 \list
78 \li paintGL() - Renders the OpenGL scene. Gets called whenever the widget
79 needs to be updated.
80 \li resizeGL() - Sets up the OpenGL viewport, projection, etc. Gets
81 called whenever the widget has been resized (and also when it
82 is shown for the first time because all newly created widgets get a
83 resize event automatically).
84 \li initializeGL() - Sets up the OpenGL resources and state. Gets called
85 once before the first time resizeGL() or paintGL() is called.
86 \endlist
87
88 If you need to trigger a repaint from places other than paintGL() (a
89 typical example is when using \l{QTimer}{timers} to animate scenes),
90 you should call the widget's update() function to schedule an update.
91
92 Your widget's OpenGL rendering context is made current when
93 paintGL(), resizeGL(), or initializeGL() is called. If you need to
94 call the standard OpenGL API functions from other places (e.g. in
95 your widget's constructor or in your own paint functions), you
96 must call makeCurrent() first.
97
98 All rendering happens into an OpenGL framebuffer
99 object. makeCurrent() ensure that it is bound in the context. Keep
100 this in mind when creating and binding additional framebuffer
101 objects in the rendering code in paintGL(). Never re-bind the
102 framebuffer with ID 0. Instead, call defaultFramebufferObject() to
103 get the ID that should be bound.
104
105 QOpenGLWidget allows using different OpenGL versions and profiles
106 when the platform supports it. Just set the requested format via
107 setFormat(). Keep in mind however that having multiple QOpenGLWidget
108 instances in the same window requires that they all use the same
109 format, or at least formats that do not make the contexts
110 non-sharable. To overcome this issue, prefer using
111 QSurfaceFormat::setDefaultFormat() instead of setFormat().
112
113 \note Calling QSurfaceFormat::setDefaultFormat() before constructing
114 the QApplication instance is mandatory on some platforms (for example,
115 \macos) when an OpenGL core profile context is requested. This is to
116 ensure that resource sharing between contexts stays functional as all
117 internal contexts are created using the correct version and profile.
118
119 \section1 Painting Techniques
120
121 As described above, subclass QOpenGLWidget to render pure 3D content in the
122 following way:
123
124 \list
125
126 \li Reimplement the initializeGL() and resizeGL() functions to
127 set up the OpenGL state and provide a perspective transformation.
128
129 \li Reimplement paintGL() to paint the 3D scene, calling only
130 OpenGL functions.
131
132 \endlist
133
134 It is also possible to draw 2D graphics onto a QOpenGLWidget subclass using QPainter:
135
136 \list
137
138 \li In paintGL(), instead of issuing OpenGL commands, construct a QPainter
139 object for use on the widget.
140
141 \li Draw primitives using QPainter's member functions.
142
143 \li Direct OpenGL commands can still be issued. However, you must make sure
144 these are enclosed by a call to the painter's beginNativePainting() and
145 endNativePainting().
146
147 \endlist
148
149 When performing drawing using QPainter only, it is also possible to perform
150 the painting like it is done for ordinary widgets: by reimplementing paintEvent().
151
152 \list
153
154 \li Reimplement the paintEvent() function.
155
156 \li Construct a QPainter object targeting the widget. Either pass the widget to the
157 constructor or the QPainter::begin() function.
158
159 \li Draw primitives using QPainter's member functions.
160
161 \li Painting finishes then the QPainter instance is destroyed. Alternatively,
162 call QPainter::end() explicitly.
163
164 \endlist
165
166 \section1 OpenGL Function Calls, Headers and QOpenGLFunctions
167
168 When making OpenGL function calls, it is strongly recommended to avoid calling
169 the functions directly. Instead, prefer using QOpenGLFunctions (when making
170 portable applications) or the versioned variants (for example,
171 QOpenGLFunctions_3_2_Core and similar, when targeting modern, desktop-only
172 OpenGL). This way the application will work correctly in all Qt build
173 configurations, including the ones that perform dynamic OpenGL implementation
174 loading which means applications are not directly linking to an GL
175 implementation and thus direct function calls are not feasible.
176
177 In paintGL() the current context is always accessible by caling
178 QOpenGLContext::currentContext(). From this context an already initialized,
179 ready-to-be-used QOpenGLFunctions instance is retrievable by calling
180 QOpenGLContext::functions(). An alternative to prefixing every GL call is to
181 inherit from QOpenGLFunctions and call
182 QOpenGLFunctions::initializeOpenGLFunctions() in initializeGL().
183
184 As for the OpenGL headers, note that in most cases there will be no need to
185 directly include any headers like GL.h. The OpenGL-related Qt headers will
186 include qopengl.h which will in turn include an appropriate header for the
187 system. This might be an OpenGL ES 3.x or 2.0 header, the highest version that
188 is available, or a system-provided gl.h. In addition, a copy of the extension
189 headers (called glext.h on some systems) is provided as part of Qt both for
190 OpenGL and OpenGL ES. These will get included automatically on platforms where
191 feasible. This means that constants and function pointer typedefs from ARB,
192 EXT, OES extensions are automatically available.
193
194 \section1 Code Examples
195
196 To get started, the simplest QOpenGLWidget subclass could like like the following:
197
198 \snippet code/doc_gui_widgets_qopenglwidget.cpp 0
199
200 Alternatively, the prefixing of each and every OpenGL call can be avoided by deriving
201 from QOpenGLFunctions instead:
202
203 \snippet code/doc_gui_widgets_qopenglwidget.cpp 1
204
205 To get a context compatible with a given OpenGL version or profile, or to
206 request depth and stencil buffers, call setFormat():
207
208 \snippet code/doc_gui_widgets_qopenglwidget.cpp 2
209
210 With OpenGL 3.0+ contexts, when portability is not important, the versioned
211 QOpenGLFunctions variants give easy access to all the modern OpenGL functions
212 available in a given version:
213
214 \snippet code/doc_gui_widgets_qopenglwidget.cpp 3
215
216 As described above, it is simpler and more robust to set the requested format
217 globally so that it applies to all windows and contexts during the lifetime of
218 the application. Below is an example of this:
219
220 \snippet code/doc_gui_widgets_qopenglwidget.cpp 6
221
222 \section1 Relation to QGLWidget
223
224 The legacy QtOpenGL module (classes prefixed with QGL) provides a widget
225 called QGLWidget. QOpenGLWidget is intended to be a modern replacement for
226 it. Therefore, especially in new applications, the general recommendation is
227 to use QOpenGLWidget.
228
229 While the API is very similar, there is an important difference between the
230 two: QOpenGLWidget always renders offscreen, using framebuffer
231 objects. QGLWidget on the other hand uses a native window and surface. The
232 latter causes issues when using it in complex user interfaces since, depending
233 on the platform, such native child widgets may have various limitations,
234 regarding stacking orders for example. QOpenGLWidget avoids this by not
235 creating a separate native window.
236
237 Due to being backed by a framebuffer object, the behavior of QOpenGLWidget is
238 very similar to QOpenGLWindow with the update behavior set to \c
239 PartialUpdateBlit or \c PartialUpdateBlend. This means that the contents are
240 preserved between paintGL() calls so that incremental rendering is
241 possible. With QGLWidget (and naturally QOpenGLWindow with the default update
242 behavior) this is usually not the case because swapping the buffers leaves the
243 back buffer with undefined contents.
244
245 \note Most applications do not need incremental rendering because they will
246 render everything in the view on every paint call. In this case it is
247 important to call glClear() as early as possible in paintGL(). This helps
248 mobile GPUs that use a tile-based architecture to recognize that the tile
249 buffer does not need to be reloaded with the framebuffer's previous
250 contents. Omitting the clear call can lead to significant performance drops on
251 such systems.
252
253 \note Avoid calling winId() on a QOpenGLWidget. This function triggers the creation of
254 a native window, resulting in reduced performance and possibly rendering glitches.
255
256 \section1 Differences to QGLWidget
257
258 Besides the main conceptual difference of being backed by a framebuffer object, there
259 are a number of smaller, internal differences between QOpenGLWidget and the older
260 QGLWidget:
261
262 \list
263
264 \li OpenGL state when invoking paintGL(). QOpenGLWidget sets up the viewport via
265 glViewport(). It does not perform any clearing.
266
267 \li Clearing when starting to paint via QPainter. Unlike regular widgets, QGLWidget
268 defaulted to a value of \c true for
269 \l{QWidget::autoFillBackground()}{autoFillBackground}. It then performed clearing to the
270 palette's background color every time QPainter::begin() was used. QOpenGLWidget does not
271 follow this: \l{QWidget::autoFillBackground()}{autoFillBackground} defaults to false,
272 like for any other widget. The only exception is when being used as a viewport for other
273 widgets like QGraphicsView. In such a case autoFillBackground will be automatically set
274 to true to ensure compatibility with QGLWidget-based viewports.
275
276 \endlist
277
278 \section1 Multisampling
279
280 To enable multisampling, set the number of requested samples on the
281 QSurfaceFormat that is passed to setFormat(). On systems that do not support
282 it the request may get ignored.
283
284 Multisampling support requires support for multisampled renderbuffers and
285 framebuffer blits. On OpenGL ES 2.0 implementations it is likely that these
286 will not be present. This means that multisampling will not be available. With
287 modern OpenGL versions and OpenGL ES 3.0 and up this is usually not a problem
288 anymore.
289
290 \section1 Threading
291
292 Performing offscreen rendering on worker threads, for example to generate
293 textures that are then used in the GUI/main thread in paintGL(), are supported
294 by exposing the widget's QOpenGLContext so that additional contexts sharing
295 with it can be created on each thread.
296
297 Drawing directly to the QOpenGLWidget's framebuffer outside the GUI/main
298 thread is possible by reimplementing paintEvent() to do nothing. The context's
299 thread affinity has to be changed via QObject::moveToThread(). After that,
300 makeCurrent() and doneCurrent() are usable on the worker thread. Be careful to
301 move the context back to the GUI/main thread afterwards.
302
303 Unlike QGLWidget, triggering a buffer swap just for the QOpenGLWidget is not
304 possible since there is no real, onscreen native surface for it. Instead, it
305 is up to the widget stack to manage composition and buffer swaps on the gui
306 thread. When a thread is done updating the framebuffer, call update() \b{on
307 the GUI/main thread} to schedule composition.
308
309 Extra care has to be taken to avoid using the framebuffer when the GUI/main
310 thread is performing compositing. The signals aboutToCompose() and
311 frameSwapped() will be emitted when the composition is starting and
312 ending. They are emitted on the GUI/main thread. This means that by using a
313 direct connection aboutToCompose() can block the GUI/main thread until the
314 worker thread has finished its rendering. After that, the worker thread must
315 perform no further rendering until the frameSwapped() signal is emitted. If
316 this is not acceptable, the worker thread has to implement a double buffering
317 mechanism. This involves drawing using an alternative render target, that is
318 fully controlled by the thread, e.g. an additional framebuffer object, and
319 blitting to the QOpenGLWidget's framebuffer at a suitable time.
320
321 \section1 Context Sharing
322
323 When multiple QOpenGLWidgets are added as children to the same top-level
324 widget, their contexts will share with each other. This does not apply for
325 QOpenGLWidget instances that belong to different windows.
326
327 This means that all QOpenGLWidgets in the same window can access each other's
328 sharable resources, like textures, and there is no need for an extra "global
329 share" context, as was the case with QGLWidget.
330
331 To set up sharing between QOpenGLWidget instances belonging to different
332 windows, set the Qt::AA_ShareOpenGLContexts application attribute before
333 instantiating QApplication. This will trigger sharing between all
334 QOpenGLWidget instances without any further steps.
335
336 Creating extra QOpenGLContext instances that share resources like textures
337 with the QOpenGLWidget's context is also possible. Simply pass the pointer
338 returned from context() to QOpenGLContext::setShareContext() before calling
339 QOpenGLContext::create(). The resulting context can also be used on a
340 different thread, allowing threaded generation of textures and asynchronous
341 texture uploads.
342
343 Note that QOpenGLWidget expects a standard conformant implementation of
344 resource sharing when it comes to the underlying graphics drivers. For
345 example, some drivers, in particular for mobile and embedded hardware, have
346 issues with setting up sharing between an existing context and others that are
347 created later. Some other drivers may behave in unexpected ways when trying to
348 utilize shared resources between different threads.
349
350 \section1 Resource Initialization and Cleanup
351
352 The QOpenGLWidget's associated OpenGL context is guaranteed to be current
353 whenever initializeGL() and paintGL() are invoked. Do not attempt to create
354 OpenGL resources before initializeGL() is called. For example, attempting to
355 compile shaders, initialize vertex buffer objects or upload texture data will
356 fail when done in a subclass's constructor. These operations must be deferred
357 to initializeGL(). Some of Qt's OpenGL helper classes, like QOpenGLBuffer or
358 QOpenGLVertexArrayObject, have a matching deferred behavior: they can be
359 instantiated without a context, but all initialization is deferred until a
360 create(), or similar, call. This means that they can be used as normal
361 (non-pointer) member variables in a QOpenGLWidget subclass, but the create()
362 or similar function can only be called from initializeGL(). Be aware however
363 that not all classes are designed like this. When in doubt, make the member
364 variable a pointer and create and destroy the instance dynamically in
365 initializeGL() and the destructor, respectively.
366
367 Releasing the resources also needs the context to be current. Therefore
368 destructors that perform such cleanup are expected to call makeCurrent()
369 before moving on to destroy any OpenGL resources or wrappers. Avoid deferred
370 deletion via \l{QObject::deleteLater()}{deleteLater()} or the parenting
371 mechanism of QObject. There is no guarantee the correct context will be
372 current at the time the instance in question is really destroyed.
373
374 A typical subclass will therefore often look like the following when it comes
375 to resource initialization and destruction:
376
377 \snippet code/doc_gui_widgets_qopenglwidget.cpp 4
378
379 This is naturally not the only possible solution. One alternative is to use
380 the \l{QOpenGLContext::aboutToBeDestroyed()}{aboutToBeDestroyed()} signal of
381 QOpenGLContext. By connecting a slot, using direct connection, to this signal,
382 it is possible to perform cleanup whenever the underlying native context
383 handle, or the entire QOpenGLContext instance, is going to be released. The
384 following snippet is in principle equivalent to the previous one:
385
386 \snippet code/doc_gui_widgets_qopenglwidget.cpp 5
387
388 \note For widgets that change their associated top-level window multiple times
389 during their lifetime, a combined approach is essential. Whenever the widget
390 or a parent of it gets reparented so that the top-level window becomes
391 different, the widget's associated context is destroyed and a new one is
392 created. This is then followed by a call to initializeGL() where all OpenGL
393 resources must get reinitialized. Due to this the only option to perform
394 proper cleanup is to connect to the context's aboutToBeDestroyed()
395 signal. Note that the context in question may not be the current one when the
396 signal gets emitted. Therefore it is good practice to call makeCurrent() in
397 the connected slot. Additionally, the same cleanup steps must be performed
398 from the derived class' destructor, since the slot connected to the signal
399 will not get invoked when the widget is being destroyed.
400
401 \note When Qt::AA_ShareOpenGLContexts is set, the widget's context never
402 changes, not even when reparenting because the widget's associated texture is
403 guaranteed to be accessible also from the new top-level's context.
404
405 Proper cleanup is especially important due to context sharing. Even though
406 each QOpenGLWidget's associated context is destroyed together with the
407 QOpenGLWidget, the sharable resources in that context, like textures, will
408 stay valid until the top-level window, in which the QOpenGLWidget lived, is
409 destroyed. Additionally, settings like Qt::AA_ShareOpenGLContexts and some Qt
410 modules may trigger an even wider scope for sharing contexts, potentially
411 leading to keeping the resources in question alive for the entire lifetime of
412 the application. Therefore the safest and most robust is always to perform
413 explicit cleanup for all resources and resource wrappers used in the
414 QOpenGLWidget.
415
416 \section1 Limitations
417
418 Putting other widgets underneath and making the QOpenGLWidget transparent will
419 not lead to the expected results: The widgets underneath will not be
420 visible. This is because in practice the QOpenGLWidget is drawn before all
421 other regular, non-OpenGL widgets, and so see-through type of solutions are
422 not feasible. Other type of layouts, like having widgets on top of the
423 QOpenGLWidget, will function as expected.
424
425 When absolutely necessary, this limitation can be overcome by setting the
426 Qt::WA_AlwaysStackOnTop attribute on the QOpenGLWidget. Be aware however that
427 this breaks stacking order, for example it will not be possible to have other
428 widgets on top of the QOpenGLWidget, so it should only be used in situations
429 where a semi-transparent QOpenGLWidget with other widgets visible underneath
430 is required.
431
432 Note that this does not apply when there are no other widgets underneath and
433 the intention is to have a semi-transparent window. In that case the
434 traditional approach of setting Qt::WA_TranslucentBackground
435 on the top-level window is sufficient. Note that if the transparent areas are
436 only desired in the QOpenGLWidget, then Qt::WA_NoSystemBackground will need
437 to be turned back to \c false after enabling Qt::WA_TranslucentBackground.
438 Additionally, requesting an alpha channel for the QOpenGLWidget's context via
439 setFormat() may be necessary too, depending on the system.
440
441 QOpenGLWidget supports multiple update behaviors, just like QOpenGLWindow. In
442 preserved mode the rendered content from the previous paintGL() call is
443 available in the next one, allowing incremental rendering. In non-preserved
444 mode the content is lost and paintGL() implementations are expected to redraw
445 everything in the view.
446
447 Before Qt 5.5 the default behavior of QOpenGLWidget was to preserve the
448 rendered contents between paintGL() calls. Since Qt 5.5 the default behavior
449 is non-preserved because this provides better performance and the majority of
450 applications have no need for the previous content. This also resembles the
451 semantics of an OpenGL-based QWindow and matches the default behavior of
452 QOpenGLWindow in that the color and ancillary buffers are invalidated for
453 each frame. To restore the preserved behavior, call setUpdateBehavior() with
454 \c PartialUpdate.
455
456 \note Displaying a QOpenGLWidget requires an alpha channel in the associated
457 top-level window's backing store due to the way composition with other
458 QWidget-based content works. If there is no alpha channel, the content
459 rendered by the QOpenGLWidget will not be visible. This can become
460 particularly relevant on Linux/X11 in remote display setups (such as, with
461 Xvnc), when using a color depth lower than 24. For example, a color depth of
462 16 will typically map to using a backing store image with the format
463 QImage::Format_RGB16 (RGB565), leaving no room for an alpha
464 channel. Therefore, if experiencing problems with getting the contents of a
465 QOpenGLWidget composited correctly with other the widgets in the window, make
466 sure the server (such as, vncserver) is configured with a 24 or 32 bit depth
467 instead of 16.
468
469 \section1 Alternatives
470
471 Adding a QOpenGLWidget into a window turns on OpenGL-based
472 compositing for the entire window. In some special cases this may
473 not be ideal, and the old QGLWidget-style behavior with a separate,
474 native child window is desired. Desktop applications that understand
475 the limitations of this approach (for example when it comes to
476 overlaps, transparency, scroll views and MDI areas), can use
477 QOpenGLWindow with QWidget::createWindowContainer(). This is a
478 modern alternative to QGLWidget and is faster than QOpenGLWidget due
479 to the lack of the additional composition step. It is strongly
480 recommended to limit the usage of this approach to cases where there
481 is no other choice. Note that this option is not suitable for most
482 embedded and mobile platforms, and it is known to have issues on
483 certain desktop platforms (e.g. \macos) too. The stable,
484 cross-platform solution is always QOpenGLWidget.
485
486 \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other
487 countries.}
488
489 \sa QOpenGLFunctions, QOpenGLWindow, Qt::AA_ShareOpenGLContexts, UpdateBehavior
490*/
491
492/*!
493 \fn void QOpenGLWidget::aboutToCompose()
494
495 This signal is emitted when the widget's top-level window is about to begin
496 composing the textures of its QOpenGLWidget children and the other widgets.
497*/
498
499/*!
500 \fn void QOpenGLWidget::frameSwapped()
501
502 This signal is emitted after the widget's top-level window has finished
503 composition and returned from its potentially blocking
504 QOpenGLContext::swapBuffers() call.
505*/
506
507/*!
508 \fn void QOpenGLWidget::aboutToResize()
509
510 This signal is emitted when the widget's size is changed and therefore the
511 framebuffer object is going to be recreated.
512*/
513
514/*!
515 \fn void QOpenGLWidget::resized()
516
517 This signal is emitted right after the framebuffer object has been recreated
518 due to resizing the widget.
519*/
520
521/*!
522 \enum QOpenGLWidget::UpdateBehavior
523 \since 5.5
524
525 This enum describes the update semantics of QOpenGLWidget.
526
527 \value NoPartialUpdate QOpenGLWidget will discard the
528 contents of the color buffer and the ancillary buffers after the
529 QOpenGLWidget is rendered to screen. This is the same behavior that can be
530 expected by calling QOpenGLContext::swapBuffers with a default opengl
531 enabled QWindow as the argument. NoPartialUpdate can have some performance
532 benefits on certain hardware architectures common in the mobile and
533 embedded space when a framebuffer object is used as the rendering target.
534 The framebuffer object is invalidated between frames with
535 glDiscardFramebufferEXT if supported or a glClear. Please see the
536 documentation of EXT_discard_framebuffer for more information:
537 https://www.khronos.org/registry/gles/extensions/EXT/EXT_discard_framebuffer.txt
538
539 \value PartialUpdate The framebuffer objects color buffer and ancillary
540 buffers are not invalidated between frames.
541
542 \sa updateBehavior(), setUpdateBehavior()
543*/
544
545class QOpenGLWidgetPaintDevicePrivate : public QOpenGLPaintDevicePrivate
546{
547public:
548 QOpenGLWidgetPaintDevicePrivate(QOpenGLWidget *widget)
549 : QOpenGLPaintDevicePrivate(QSize()),
550 w(widget) { }
551
552 void beginPaint() override;
553 void endPaint() override;
554
555 QOpenGLWidget *w;
556};
557
558class QOpenGLWidgetPaintDevice : public QOpenGLPaintDevice
559{
560public:
561 QOpenGLWidgetPaintDevice(QOpenGLWidget *widget)
562 : QOpenGLPaintDevice(*new QOpenGLWidgetPaintDevicePrivate(widget)) { }
563 void ensureActiveTarget() override;
564};
565
566class QOpenGLWidgetPrivate : public QWidgetPrivate
567{
568 Q_DECLARE_PUBLIC(QOpenGLWidget)
569public:
570 QOpenGLWidgetPrivate()
571 : context(nullptr),
572 fbo(nullptr),
573 resolvedFbo(nullptr),
574 surface(nullptr),
575 initialized(false),
576 fakeHidden(false),
577 inBackingStorePaint(false),
578 hasBeenComposed(false),
579 flushPending(false),
580 paintDevice(nullptr),
581 updateBehavior(QOpenGLWidget::NoPartialUpdate),
582 requestedSamples(0),
583 inPaintGL(false),
584 textureFormat(0)
585 {
586 requestedFormat = QSurfaceFormat::defaultFormat();
587 }
588
589 void reset();
590 void recreateFbo();
591
592 GLuint textureId() const override;
593 QPlatformTextureList::Flags textureListFlags() override;
594
595 void initialize();
596 void invokeUserPaint();
597 void render();
598
599 void invalidateFbo();
600
601 QImage grabFramebuffer() override;
602 void beginBackingStorePainting() override { inBackingStorePaint = true; }
603 void endBackingStorePainting() override { inBackingStorePaint = false; }
604 void beginCompose() override;
605 void endCompose() override;
606 void initializeViewportFramebuffer() override;
607 void resizeViewportFramebuffer() override;
608 void resolveSamples() override;
609
610 QOpenGLContext *context;
611 QOpenGLFramebufferObject *fbo;
612 QOpenGLFramebufferObject *resolvedFbo;
613 QOffscreenSurface *surface;
614 bool initialized;
615 bool fakeHidden;
616 bool inBackingStorePaint;
617 bool hasBeenComposed;
618 bool flushPending;
619 QOpenGLPaintDevice *paintDevice;
620 QSurfaceFormat requestedFormat;
621 QOpenGLWidget::UpdateBehavior updateBehavior;
622 int requestedSamples;
623 bool inPaintGL;
624 GLenum textureFormat;
625};
626
627void QOpenGLWidgetPaintDevicePrivate::beginPaint()
628{
629 // NB! autoFillBackground is and must be false by default. Otherwise we would clear on
630 // every QPainter begin() which is not desirable. This is only for legacy use cases,
631 // like using QOpenGLWidget as the viewport of a graphics view, that expect clearing
632 // with the palette's background color.
633 if (w->autoFillBackground()) {
634 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
635 if (w->format().hasAlpha()) {
636 f->glClearColor(red: 0, green: 0, blue: 0, alpha: 0);
637 } else {
638 QColor c = w->palette().brush(cr: w->backgroundRole()).color();
639 float alpha = c.alphaF();
640 f->glClearColor(red: c.redF() * alpha, green: c.greenF() * alpha, blue: c.blueF() * alpha, alpha);
641 }
642 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
643 }
644}
645
646void QOpenGLWidgetPaintDevicePrivate::endPaint()
647{
648 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(w));
649 if (!wd->initialized)
650 return;
651
652 if (!wd->inPaintGL)
653 QOpenGLContextPrivate::get(context: wd->context)->defaultFboRedirect = 0;
654}
655
656void QOpenGLWidgetPaintDevice::ensureActiveTarget()
657{
658 QOpenGLWidgetPaintDevicePrivate *d = static_cast<QOpenGLWidgetPaintDevicePrivate *>(d_ptr.data());
659 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(w: d->w));
660 if (!wd->initialized)
661 return;
662
663 if (QOpenGLContext::currentContext() != wd->context)
664 d->w->makeCurrent();
665 else
666 wd->fbo->bind();
667
668 if (!wd->inPaintGL)
669 QOpenGLContextPrivate::get(context: wd->context)->defaultFboRedirect = wd->fbo->handle();
670
671 // When used as a viewport, drawing is done via opening a QPainter on the widget
672 // without going through paintEvent(). We will have to make sure a glFlush() is done
673 // before the texture is accessed also in this case.
674 wd->flushPending = true;
675}
676
677GLuint QOpenGLWidgetPrivate::textureId() const
678{
679 return resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0);
680}
681
682#ifndef GL_SRGB
683#define GL_SRGB 0x8C40
684#endif
685#ifndef GL_SRGB8
686#define GL_SRGB8 0x8C41
687#endif
688#ifndef GL_SRGB_ALPHA
689#define GL_SRGB_ALPHA 0x8C42
690#endif
691#ifndef GL_SRGB8_ALPHA8
692#define GL_SRGB8_ALPHA8 0x8C43
693#endif
694
695QPlatformTextureList::Flags QOpenGLWidgetPrivate::textureListFlags()
696{
697 QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags();
698 switch (textureFormat) {
699 case GL_SRGB:
700 case GL_SRGB8:
701 case GL_SRGB_ALPHA:
702 case GL_SRGB8_ALPHA8:
703 flags |= QPlatformTextureList::TextureIsSrgb;
704 break;
705 default:
706 break;
707 }
708 return flags;
709}
710
711void QOpenGLWidgetPrivate::reset()
712{
713 Q_Q(QOpenGLWidget);
714
715 // Destroy the OpenGL resources first. These need the context to be current.
716 if (initialized)
717 q->makeCurrent();
718
719 delete paintDevice;
720 paintDevice = nullptr;
721 delete fbo;
722 fbo = nullptr;
723 delete resolvedFbo;
724 resolvedFbo = nullptr;
725
726 if (initialized)
727 q->doneCurrent();
728
729 // Delete the context first, then the surface. Slots connected to
730 // the context's aboutToBeDestroyed() may still call makeCurrent()
731 // to perform some cleanup.
732 delete context;
733 context = nullptr;
734 delete surface;
735 surface = nullptr;
736 initialized = fakeHidden = inBackingStorePaint = false;
737}
738
739void QOpenGLWidgetPrivate::recreateFbo()
740{
741 Q_Q(QOpenGLWidget);
742
743 emit q->aboutToResize();
744
745 context->makeCurrent(surface);
746
747 delete fbo;
748 fbo = nullptr;
749 delete resolvedFbo;
750 resolvedFbo = nullptr;
751
752 int samples = requestedSamples;
753 QOpenGLExtensions *extfuncs = static_cast<QOpenGLExtensions *>(context->functions());
754 if (!extfuncs->hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample))
755 samples = 0;
756
757 QOpenGLFramebufferObjectFormat format;
758 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
759 format.setSamples(samples);
760 if (textureFormat)
761 format.setInternalTextureFormat(textureFormat);
762
763 const QSize deviceSize = q->size() * q->devicePixelRatioF();
764 fbo = new QOpenGLFramebufferObject(deviceSize, format);
765 if (samples > 0)
766 resolvedFbo = new QOpenGLFramebufferObject(deviceSize);
767
768 textureFormat = fbo->format().internalTextureFormat();
769
770 fbo->bind();
771 context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
772 flushPending = true; // Make sure the FBO is initialized before use
773
774 paintDevice->setSize(deviceSize);
775 paintDevice->setDevicePixelRatio(q->devicePixelRatioF());
776
777 emit q->resized();
778}
779
780void QOpenGLWidgetPrivate::beginCompose()
781{
782 Q_Q(QOpenGLWidget);
783 if (flushPending) {
784 flushPending = false;
785 q->makeCurrent();
786 static_cast<QOpenGLExtensions *>(context->functions())->flushShared();
787 }
788 hasBeenComposed = true;
789 emit q->aboutToCompose();
790}
791
792void QOpenGLWidgetPrivate::endCompose()
793{
794 Q_Q(QOpenGLWidget);
795 emit q->frameSwapped();
796}
797
798void QOpenGLWidgetPrivate::initialize()
799{
800 Q_Q(QOpenGLWidget);
801 if (initialized)
802 return;
803
804 // If no global shared context get our toplevel's context with which we
805 // will share in order to make the texture usable by the underlying window's backingstore.
806 QWidget *tlw = q->window();
807 QOpenGLContext *shareContext = qt_gl_global_share_context();
808 if (!shareContext)
809 shareContext = get(w: tlw)->shareContext();
810 // If shareContext is null, showing content on-screen will not work.
811 // However, offscreen rendering and grabFramebuffer() will stay fully functional.
812
813 // Do not include the sample count. Requesting a multisampled context is not necessary
814 // since we render into an FBO, never to an actual surface. What's more, attempting to
815 // create a pbuffer with a multisampled config crashes certain implementations. Just
816 // avoid the entire hassle, the result is the same.
817 requestedSamples = requestedFormat.samples();
818 requestedFormat.setSamples(0);
819
820 QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext);
821 ctx->setFormat(requestedFormat);
822 if (shareContext) {
823 ctx->setShareContext(shareContext);
824 ctx->setScreen(shareContext->screen());
825 }
826 if (Q_UNLIKELY(!ctx->create())) {
827 qWarning(msg: "QOpenGLWidget: Failed to create context");
828 return;
829 }
830
831 // Propagate settings that make sense only for the tlw. Note that this only
832 // makes sense for properties that get picked up even after the native
833 // window is created.
834 if (tlw->windowHandle()) {
835 QSurfaceFormat tlwFormat = tlw->windowHandle()->format();
836 if (requestedFormat.swapInterval() != tlwFormat.swapInterval()) {
837 // Most platforms will pick up the changed swap interval on the next
838 // makeCurrent or swapBuffers.
839 tlwFormat.setSwapInterval(requestedFormat.swapInterval());
840 tlw->windowHandle()->setFormat(tlwFormat);
841 }
842 if (requestedFormat.swapBehavior() != tlwFormat.swapBehavior()) {
843 tlwFormat.setSwapBehavior(requestedFormat.swapBehavior());
844 tlw->windowHandle()->setFormat(tlwFormat);
845 }
846 }
847
848 // The top-level window's surface is not good enough since it causes way too
849 // much trouble with regards to the QSurfaceFormat for example. So just like
850 // in QQuickWidget, use a dedicated QOffscreenSurface.
851 surface = new QOffscreenSurface;
852 surface->setFormat(ctx->format());
853 surface->setScreen(ctx->screen());
854 surface->create();
855
856 if (Q_UNLIKELY(!ctx->makeCurrent(surface))) {
857 qWarning(msg: "QOpenGLWidget: Failed to make context current");
858 return;
859 }
860
861 paintDevice = new QOpenGLWidgetPaintDevice(q);
862 paintDevice->setSize(q->size() * q->devicePixelRatioF());
863 paintDevice->setDevicePixelRatio(q->devicePixelRatioF());
864
865 context = ctx.take();
866 initialized = true;
867
868 q->initializeGL();
869}
870
871void QOpenGLWidgetPrivate::resolveSamples()
872{
873 Q_Q(QOpenGLWidget);
874 if (resolvedFbo) {
875 q->makeCurrent();
876 QRect rect(QPoint(0, 0), fbo->size());
877 QOpenGLFramebufferObject::blitFramebuffer(target: resolvedFbo, targetRect: rect, source: fbo, sourceRect: rect);
878 flushPending = true;
879 }
880}
881
882void QOpenGLWidgetPrivate::invokeUserPaint()
883{
884 Q_Q(QOpenGLWidget);
885
886 QOpenGLContext *ctx = QOpenGLContext::currentContext();
887 Q_ASSERT(ctx && fbo);
888
889 QOpenGLFunctions *f = ctx->functions();
890 QOpenGLContextPrivate::get(context: ctx)->defaultFboRedirect = fbo->handle();
891
892 f->glViewport(x: 0, y: 0, width: q->width() * q->devicePixelRatioF(), height: q->height() * q->devicePixelRatioF());
893 inPaintGL = true;
894 q->paintGL();
895 inPaintGL = false;
896 flushPending = true;
897
898 QOpenGLContextPrivate::get(context: ctx)->defaultFboRedirect = 0;
899}
900
901void QOpenGLWidgetPrivate::render()
902{
903 Q_Q(QOpenGLWidget);
904
905 if (fakeHidden || !initialized)
906 return;
907
908 q->makeCurrent();
909
910 if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
911 invalidateFbo();
912 hasBeenComposed = false;
913 }
914
915 invokeUserPaint();
916}
917
918void QOpenGLWidgetPrivate::invalidateFbo()
919{
920 QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
921 if (f->hasOpenGLExtension(extension: QOpenGLExtensions::DiscardFramebuffer)) {
922 const int gl_color_attachment0 = 0x8CE0; // GL_COLOR_ATTACHMENT0
923 const int gl_depth_attachment = 0x8D00; // GL_DEPTH_ATTACHMENT
924 const int gl_stencil_attachment = 0x8D20; // GL_STENCIL_ATTACHMENT
925#ifdef Q_OS_WASM
926 // webgl does not allow separate depth and stencil attachments
927 // QTBUG-69913
928 const int gl_depth_stencil_attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT
929
930 const GLenum attachments[] = {
931 gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment, gl_depth_stencil_attachment
932 };
933#else
934 const GLenum attachments[] = {
935 gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment
936 };
937#endif
938 f->glDiscardFramebufferEXT(GL_FRAMEBUFFER, numAttachments: sizeof attachments / sizeof *attachments, attachments);
939 } else {
940 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
941 }
942}
943
944extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
945
946QImage QOpenGLWidgetPrivate::grabFramebuffer()
947{
948 Q_Q(QOpenGLWidget);
949
950 initialize();
951 if (!initialized)
952 return QImage();
953
954 if (!fbo) // could be completely offscreen, without ever getting a resize event
955 recreateFbo();
956
957 if (!inPaintGL)
958 render();
959
960 if (resolvedFbo) {
961 resolveSamples();
962 resolvedFbo->bind();
963 } else {
964 q->makeCurrent();
965 }
966
967 const bool hasAlpha = q->format().hasAlpha();
968 QImage res = qt_gl_read_framebuffer(size: q->size() * q->devicePixelRatioF(), alpha_format: hasAlpha, include_alpha: hasAlpha);
969 res.setDevicePixelRatio(q->devicePixelRatioF());
970
971 // While we give no guarantees of what is going to be left bound, prefer the
972 // multisample fbo instead of the resolved one. Clients may continue to
973 // render straight after calling this function.
974 if (resolvedFbo)
975 q->makeCurrent();
976
977 return res;
978}
979
980void QOpenGLWidgetPrivate::initializeViewportFramebuffer()
981{
982 Q_Q(QOpenGLWidget);
983 // Legacy behavior for compatibility with QGLWidget when used as a graphics view
984 // viewport: enable clearing on each painter begin.
985 q->setAutoFillBackground(true);
986}
987
988void QOpenGLWidgetPrivate::resizeViewportFramebuffer()
989{
990 Q_Q(QOpenGLWidget);
991 if (!initialized)
992 return;
993
994 if (!fbo || q->size() * q->devicePixelRatioF() != fbo->size()) {
995 recreateFbo();
996 q->update();
997 }
998}
999
1000/*!
1001 Constructs a widget which is a child of \a parent, with widget flags set to \a f.
1002 */
1003QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f)
1004 : QWidget(*(new QOpenGLWidgetPrivate), parent, f)
1005{
1006 Q_D(QOpenGLWidget);
1007 if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)))
1008 qWarning(msg: "QOpenGLWidget is not supported on this platform.");
1009 else
1010 d->setRenderToTexture();
1011}
1012
1013/*!
1014 Destroys the QOpenGLWidget instance, freeing its resources.
1015
1016 The QOpenGLWidget's context is made current in the destructor, allowing for
1017 safe destruction of any child object that may need to release OpenGL
1018 resources belonging to the context provided by this widget.
1019
1020 \warning if you have objects wrapping OpenGL resources (such as
1021 QOpenGLBuffer, QOpenGLShaderProgram, etc.) as members of a OpenGLWidget
1022 subclass, you may need to add a call to makeCurrent() in that subclass'
1023 destructor as well. Due to the rules of C++ object destruction, those objects
1024 will be destroyed \e{before} calling this function (but after that the
1025 destructor of the subclass has run), therefore making the OpenGL context
1026 current in this function happens too late for their safe disposal.
1027
1028 \sa makeCurrent
1029*/
1030QOpenGLWidget::~QOpenGLWidget()
1031{
1032 Q_D(QOpenGLWidget);
1033 d->reset();
1034}
1035
1036/*!
1037 Sets this widget's update behavior to \a updateBehavior.
1038 \since 5.5
1039*/
1040void QOpenGLWidget::setUpdateBehavior(UpdateBehavior updateBehavior)
1041{
1042 Q_D(QOpenGLWidget);
1043 d->updateBehavior = updateBehavior;
1044}
1045
1046/*!
1047 \return the update behavior of the widget.
1048 \since 5.5
1049*/
1050QOpenGLWidget::UpdateBehavior QOpenGLWidget::updateBehavior() const
1051{
1052 Q_D(const QOpenGLWidget);
1053 return d->updateBehavior;
1054}
1055
1056/*!
1057 Sets the requested surface \a format.
1058
1059 When the format is not explicitly set via this function, the format returned by
1060 QSurfaceFormat::defaultFormat() will be used. This means that when having multiple
1061 OpenGL widgets, individual calls to this function can be replaced by one single call to
1062 QSurfaceFormat::setDefaultFormat() before creating the first widget.
1063
1064 \note Requesting an alpha buffer via this function will not lead to the
1065 desired results when the intention is to make other widgets beneath visible.
1066 Instead, use Qt::WA_AlwaysStackOnTop to enable semi-transparent QOpenGLWidget
1067 instances with other widgets visible underneath. Keep in mind however that
1068 this breaks the stacking order, so it will no longer be possible to have
1069 other widgets on top of the QOpenGLWidget.
1070
1071 \sa format(), Qt::WA_AlwaysStackOnTop, QSurfaceFormat::setDefaultFormat()
1072 */
1073void QOpenGLWidget::setFormat(const QSurfaceFormat &format)
1074{
1075 Q_D(QOpenGLWidget);
1076 if (Q_UNLIKELY(d->initialized)) {
1077 qWarning(msg: "QOpenGLWidget: Already initialized, setting the format has no effect");
1078 return;
1079 }
1080
1081 d->requestedFormat = format;
1082}
1083
1084/*!
1085 Returns the context and surface format used by this widget and its toplevel
1086 window.
1087
1088 After the widget and its toplevel have both been created, resized and shown,
1089 this function will return the actual format of the context. This may differ
1090 from the requested format if the request could not be fulfilled by the
1091 platform. It is also possible to get larger color buffer sizes than
1092 requested.
1093
1094 When the widget's window and the related OpenGL resources are not yet
1095 initialized, the return value is the format that has been set via
1096 setFormat().
1097
1098 \sa setFormat(), context()
1099 */
1100QSurfaceFormat QOpenGLWidget::format() const
1101{
1102 Q_D(const QOpenGLWidget);
1103 return d->initialized ? d->context->format() : d->requestedFormat;
1104}
1105
1106/*!
1107 Sets a custom internal texture format of \a texFormat.
1108
1109 When working with sRGB framebuffers, it will be necessary to specify a
1110 format like \c{GL_SRGB8_ALPHA8}. This can be achieved by calling this
1111 function.
1112
1113 \note This function has no effect if called after the widget has already
1114 been shown and thus it performed initialization.
1115
1116 \note This function will typically have to be used in combination with a
1117 QSurfaceFormat::setDefaultFormat() call that sets the color space to
1118 QSurfaceFormat::sRGBColorSpace.
1119
1120 \since 5.10
1121 */
1122void QOpenGLWidget::setTextureFormat(GLenum texFormat)
1123{
1124 Q_D(QOpenGLWidget);
1125 if (Q_UNLIKELY(d->initialized)) {
1126 qWarning(msg: "QOpenGLWidget: Already initialized, setting the internal texture format has no effect");
1127 return;
1128 }
1129
1130 d->textureFormat = texFormat;
1131}
1132
1133/*!
1134 \return the active internal texture format if the widget has already
1135 initialized, the requested format if one was set but the widget has not yet
1136 been made visible, or \nullptr if setTextureFormat() was not called and the
1137 widget has not yet been made visible.
1138
1139 \since 5.10
1140 */
1141GLenum QOpenGLWidget::textureFormat() const
1142{
1143 Q_D(const QOpenGLWidget);
1144 return d->textureFormat;
1145}
1146
1147/*!
1148 \return \e true if the widget and OpenGL resources, like the context, have
1149 been successfully initialized. Note that the return value is always false
1150 until the widget is shown.
1151*/
1152bool QOpenGLWidget::isValid() const
1153{
1154 Q_D(const QOpenGLWidget);
1155 return d->initialized && d->context->isValid();
1156}
1157
1158/*!
1159 Prepares for rendering OpenGL content for this widget by making the
1160 corresponding context current and binding the framebuffer object in that
1161 context.
1162
1163 It is not necessary to call this function in most cases, because it
1164 is called automatically before invoking paintGL().
1165
1166 \sa context(), paintGL(), doneCurrent()
1167 */
1168void QOpenGLWidget::makeCurrent()
1169{
1170 Q_D(QOpenGLWidget);
1171 if (!d->initialized)
1172 return;
1173
1174 d->context->makeCurrent(surface: d->surface);
1175
1176 if (d->fbo) // there may not be one if we are in reset()
1177 d->fbo->bind();
1178}
1179
1180/*!
1181 Releases the context.
1182
1183 It is not necessary to call this function in most cases, since the
1184 widget will make sure the context is bound and released properly
1185 when invoking paintGL().
1186 */
1187void QOpenGLWidget::doneCurrent()
1188{
1189 Q_D(QOpenGLWidget);
1190 if (!d->initialized)
1191 return;
1192
1193 d->context->doneCurrent();
1194}
1195
1196/*!
1197 \return The QOpenGLContext used by this widget or \c 0 if not yet initialized.
1198
1199 \note The context and the framebuffer object used by the widget changes when
1200 reparenting the widget via setParent().
1201
1202 \sa QOpenGLContext::setShareContext(), defaultFramebufferObject()
1203 */
1204QOpenGLContext *QOpenGLWidget::context() const
1205{
1206 Q_D(const QOpenGLWidget);
1207 return d->context;
1208}
1209
1210/*!
1211 \return The framebuffer object handle or \c 0 if not yet initialized.
1212
1213 \note The framebuffer object belongs to the context returned by context()
1214 and may not be accessible from other contexts.
1215
1216 \note The context and the framebuffer object used by the widget changes when
1217 reparenting the widget via setParent(). In addition, the framebuffer object
1218 changes on each resize.
1219
1220 \sa context()
1221 */
1222GLuint QOpenGLWidget::defaultFramebufferObject() const
1223{
1224 Q_D(const QOpenGLWidget);
1225 return d->fbo ? d->fbo->handle() : 0;
1226}
1227
1228/*!
1229 This virtual function is called once before the first call to
1230 paintGL() or resizeGL(). Reimplement it in a subclass.
1231
1232 This function should set up any required OpenGL resources and state.
1233
1234 There is no need to call makeCurrent() because this has already been
1235 done when this function is called. Note however that the framebuffer
1236 is not yet available at this stage, so avoid issuing draw calls from
1237 here. Defer such calls to paintGL() instead.
1238
1239 \sa paintGL(), resizeGL()
1240*/
1241void QOpenGLWidget::initializeGL()
1242{
1243}
1244
1245/*!
1246 This virtual function is called whenever the widget has been
1247 resized. Reimplement it in a subclass. The new size is passed in
1248 \a w and \a h.
1249
1250 There is no need to call makeCurrent() because this has already been
1251 done when this function is called. Additionally, the framebuffer is
1252 also bound.
1253
1254 \sa initializeGL(), paintGL()
1255*/
1256void QOpenGLWidget::resizeGL(int w, int h)
1257{
1258 Q_UNUSED(w);
1259 Q_UNUSED(h);
1260}
1261
1262/*!
1263 This virtual function is called whenever the widget needs to be
1264 painted. Reimplement it in a subclass.
1265
1266 There is no need to call makeCurrent() because this has already
1267 been done when this function is called.
1268
1269 Before invoking this function, the context and the framebuffer are
1270 bound, and the viewport is set up by a call to glViewport(). No
1271 other state is set and no clearing or drawing is performed by the
1272 framework.
1273
1274 \sa initializeGL(), resizeGL()
1275*/
1276void QOpenGLWidget::paintGL()
1277{
1278}
1279
1280/*!
1281 Handles resize events that are passed in the \a e event parameter.
1282 Calls the virtual function resizeGL().
1283
1284 \note Avoid overriding this function in derived classes. If that is not
1285 feasible, make sure that QOpenGLWidget's implementation is invoked
1286 too. Otherwise the underlying framebuffer object and related resources will
1287 not get resized properly and will lead to incorrect rendering.
1288*/
1289void QOpenGLWidget::resizeEvent(QResizeEvent *e)
1290{
1291 Q_D(QOpenGLWidget);
1292
1293 if (e->size().isEmpty()) {
1294 d->fakeHidden = true;
1295 return;
1296 }
1297 d->fakeHidden = false;
1298
1299 d->initialize();
1300 if (!d->initialized)
1301 return;
1302
1303 d->recreateFbo();
1304 resizeGL(w: width(), h: height());
1305 d->sendPaintEvent(toBePainted: QRect(QPoint(0, 0), size()));
1306}
1307
1308/*!
1309 Handles paint events.
1310
1311 Calling QWidget::update() will lead to sending a paint event \a e,
1312 and thus invoking this function. (NB this is asynchronous and will
1313 happen at some point after returning from update()). This function
1314 will then, after some preparation, call the virtual paintGL() to
1315 update the contents of the QOpenGLWidget's framebuffer. The widget's
1316 top-level window will then composite the framebuffer's texture with
1317 the rest of the window.
1318*/
1319void QOpenGLWidget::paintEvent(QPaintEvent *e)
1320{
1321 Q_UNUSED(e);
1322 Q_D(QOpenGLWidget);
1323 if (!d->initialized)
1324 return;
1325
1326 if (updatesEnabled())
1327 d->render();
1328}
1329
1330/*!
1331 Renders and returns a 32-bit RGB image of the framebuffer.
1332
1333 \note This is a potentially expensive operation because it relies on glReadPixels()
1334 to read back the pixels. This may be slow and can stall the GPU pipeline.
1335*/
1336QImage QOpenGLWidget::grabFramebuffer()
1337{
1338 Q_D(QOpenGLWidget);
1339 return d->grabFramebuffer();
1340}
1341
1342/*!
1343 \reimp
1344*/
1345int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
1346{
1347 Q_D(const QOpenGLWidget);
1348 if (d->inBackingStorePaint)
1349 return QWidget::metric(metric);
1350
1351 auto window = d->windowHandle(mode: QWidgetPrivate::WindowHandleMode::TopLevel);
1352 QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
1353
1354 const float dpmx = qt_defaultDpiX() * 100. / 2.54;
1355 const float dpmy = qt_defaultDpiY() * 100. / 2.54;
1356
1357 switch (metric) {
1358 case PdmWidth:
1359 return width();
1360 case PdmHeight:
1361 return height();
1362 case PdmDepth:
1363 return 32;
1364 case PdmWidthMM:
1365 if (screen)
1366 return width() * screen->physicalSize().width() / screen->geometry().width();
1367 else
1368 return width() * 1000 / dpmx;
1369 case PdmHeightMM:
1370 if (screen)
1371 return height() * screen->physicalSize().height() / screen->geometry().height();
1372 else
1373 return height() * 1000 / dpmy;
1374 case PdmNumColors:
1375 return 0;
1376 case PdmDpiX:
1377 if (screen)
1378 return qRound(d: screen->logicalDotsPerInchX());
1379 else
1380 return qRound(d: dpmx * 0.0254);
1381 case PdmDpiY:
1382 if (screen)
1383 return qRound(d: screen->logicalDotsPerInchY());
1384 else
1385 return qRound(d: dpmy * 0.0254);
1386 case PdmPhysicalDpiX:
1387 if (screen)
1388 return qRound(d: screen->physicalDotsPerInchX());
1389 else
1390 return qRound(d: dpmx * 0.0254);
1391 case PdmPhysicalDpiY:
1392 if (screen)
1393 return qRound(d: screen->physicalDotsPerInchY());
1394 else
1395 return qRound(d: dpmy * 0.0254);
1396 case PdmDevicePixelRatio:
1397 if (window)
1398 return int(window->devicePixelRatio());
1399 else
1400 return 1.0;
1401 case PdmDevicePixelRatioScaled:
1402 if (window)
1403 return int(window->devicePixelRatio() * devicePixelRatioFScale());
1404 else
1405 return int(devicePixelRatioFScale());
1406 default:
1407 qWarning(msg: "QOpenGLWidget::metric(): unknown metric %d", metric);
1408 return 0;
1409 }
1410}
1411
1412/*!
1413 \reimp
1414*/
1415QPaintDevice *QOpenGLWidget::redirected(QPoint *p) const
1416{
1417 Q_D(const QOpenGLWidget);
1418 if (d->inBackingStorePaint)
1419 return QWidget::redirected(offset: p);
1420
1421 return d->paintDevice;
1422}
1423
1424/*!
1425 \reimp
1426*/
1427QPaintEngine *QOpenGLWidget::paintEngine() const
1428{
1429 Q_D(const QOpenGLWidget);
1430 // QWidget needs to "punch a hole" into the backingstore. This needs the
1431 // normal paint engine and device, not the GL one. So in this mode, behave
1432 // like a normal widget.
1433 if (d->inBackingStorePaint)
1434 return QWidget::paintEngine();
1435
1436 if (!d->initialized)
1437 return nullptr;
1438
1439 return d->paintDevice->paintEngine();
1440}
1441
1442/*!
1443 \reimp
1444*/
1445bool QOpenGLWidget::event(QEvent *e)
1446{
1447 Q_D(QOpenGLWidget);
1448 switch (e->type()) {
1449 case QEvent::WindowChangeInternal:
1450 if (QCoreApplication::testAttribute(attribute: Qt::AA_ShareOpenGLContexts))
1451 break;
1452 if (d->initialized)
1453 d->reset();
1454 if (isHidden())
1455 break;
1456 Q_FALLTHROUGH();
1457 case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too
1458 if (d->initialized && window()->windowHandle()
1459 && d->context->shareContext() != QWidgetPrivate::get(w: window())->shareContext())
1460 {
1461 // Special case: did grabFramebuffer() for a hidden widget that then became visible.
1462 // Recreate all resources since the context now needs to share with the TLW's.
1463 if (!QCoreApplication::testAttribute(attribute: Qt::AA_ShareOpenGLContexts))
1464 d->reset();
1465 }
1466 if (!d->initialized && !size().isEmpty() && window()->windowHandle()) {
1467 d->initialize();
1468 if (d->initialized) {
1469 d->recreateFbo();
1470 // QTBUG-89812: generate a paint event, like resize would do,
1471 // otherwise a QOpenGLWidget in a QDockWidget may not show the
1472 // content upon (un)docking.
1473 d->sendPaintEvent(toBePainted: QRect(QPoint(0, 0), size()));
1474 }
1475 }
1476 break;
1477 case QEvent::ScreenChangeInternal:
1478 if (d->initialized && d->paintDevice->devicePixelRatioF() != devicePixelRatioF())
1479 d->recreateFbo();
1480 break;
1481 default:
1482 break;
1483 }
1484 return QWidget::event(event: e);
1485}
1486
1487QT_END_NAMESPACE
1488
1489#include "moc_qopenglwidget.cpp"
1490

source code of qtbase/src/widgets/kernel/qopenglwidget.cpp