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
58 QT_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 \section1 Alternatives
457
458 Adding a QOpenGLWidget into a window turns on OpenGL-based
459 compositing for the entire window. In some special cases this may
460 not be ideal, and the old QGLWidget-style behavior with a separate,
461 native child window is desired. Desktop applications that understand
462 the limitations of this approach (for example when it comes to
463 overlaps, transparency, scroll views and MDI areas), can use
464 QOpenGLWindow with QWidget::createWindowContainer(). This is a
465 modern alternative to QGLWidget and is faster than QOpenGLWidget due
466 to the lack of the additional composition step. It is strongly
467 recommended to limit the usage of this approach to cases where there
468 is no other choice. Note that this option is not suitable for most
469 embedded and mobile platforms, and it is known to have issues on
470 certain desktop platforms (e.g. \macos) too. The stable,
471 cross-platform solution is always QOpenGLWidget.
472
473 \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other
474 countries.}
475
476 \sa QOpenGLFunctions, QOpenGLWindow, Qt::AA_ShareOpenGLContexts, UpdateBehavior
477 */
478
479 /*!
480 \fn void QOpenGLWidget::aboutToCompose()
481
482 This signal is emitted when the widget's top-level window is about to begin
483 composing the textures of its QOpenGLWidget children and the other widgets.
484 */
485
486 /*!
487 \fn void QOpenGLWidget::frameSwapped()
488
489 This signal is emitted after the widget's top-level window has finished
490 composition and returned from its potentially blocking
491 QOpenGLContext::swapBuffers() call.
492 */
493
494 /*!
495 \fn void QOpenGLWidget::aboutToResize()
496
497 This signal is emitted when the widget's size is changed and therefore the
498 framebuffer object is going to be recreated.
499 */
500
501 /*!
502 \fn void QOpenGLWidget::resized()
503
504 This signal is emitted right after the framebuffer object has been recreated
505 due to resizing the widget.
506 */
507
508 /*!
509 \enum QOpenGLWidget::UpdateBehavior
510 \since 5.5
511
512 This enum describes the update semantics of QOpenGLWidget.
513
514 \value NoPartialUpdate QOpenGLWidget will discard the
515 contents of the color buffer and the ancillary buffers after the
516 QOpenGLWidget is rendered to screen. This is the same behavior that can be
517 expected by calling QOpenGLContext::swapBuffers with a default opengl
518 enabled QWindow as the argument. NoPartialUpdate can have some performance
519 benefits on certain hardware architectures common in the mobile and
520 embedded space when a framebuffer object is used as the rendering target.
521 The framebuffer object is invalidated between frames with
522 glDiscardFramebufferEXT if supported or a glClear. Please see the
523 documentation of EXT_discard_framebuffer for more information:
524 https://www.khronos.org/registry/gles/extensions/EXT/EXT_discard_framebuffer.txt
525
526 \value PartialUpdate The framebuffer objects color buffer and ancillary
527 buffers are not invalidated between frames.
528
529 \sa updateBehavior(), setUpdateBehavior()
530 */
531
532 class QOpenGLWidgetPaintDevicePrivate : public QOpenGLPaintDevicePrivate
533 {
534 public:
QOpenGLWidgetPaintDevicePrivate(QOpenGLWidget * widget)535 QOpenGLWidgetPaintDevicePrivate(QOpenGLWidget *widget)
536 : QOpenGLPaintDevicePrivate(QSize()),
537 w(widget) { }
538
539 void beginPaint() override;
540 void endPaint() override;
541
542 QOpenGLWidget *w;
543 };
544
545 class QOpenGLWidgetPaintDevice : public QOpenGLPaintDevice
546 {
547 public:
QOpenGLWidgetPaintDevice(QOpenGLWidget * widget)548 QOpenGLWidgetPaintDevice(QOpenGLWidget *widget)
549 : QOpenGLPaintDevice(*new QOpenGLWidgetPaintDevicePrivate(widget)) { }
550 void ensureActiveTarget() override;
551 };
552
553 class QOpenGLWidgetPrivate : public QWidgetPrivate
554 {
555 Q_DECLARE_PUBLIC(QOpenGLWidget)
556 public:
QOpenGLWidgetPrivate()557 QOpenGLWidgetPrivate()
558 : context(nullptr),
559 fbo(nullptr),
560 resolvedFbo(nullptr),
561 surface(nullptr),
562 initialized(false),
563 fakeHidden(false),
564 inBackingStorePaint(false),
565 hasBeenComposed(false),
566 flushPending(false),
567 paintDevice(nullptr),
568 updateBehavior(QOpenGLWidget::NoPartialUpdate),
569 requestedSamples(0),
570 inPaintGL(false),
571 textureFormat(0)
572 {
573 requestedFormat = QSurfaceFormat::defaultFormat();
574 }
575
576 void reset();
577 void recreateFbo();
578
579 GLuint textureId() const override;
580 QPlatformTextureList::Flags textureListFlags() override;
581
582 void initialize();
583 void invokeUserPaint();
584 void render();
585
586 void invalidateFbo();
587
588 QImage grabFramebuffer() override;
beginBackingStorePainting()589 void beginBackingStorePainting() override { inBackingStorePaint = true; }
endBackingStorePainting()590 void endBackingStorePainting() override { inBackingStorePaint = false; }
591 void beginCompose() override;
592 void endCompose() override;
593 void initializeViewportFramebuffer() override;
594 void resizeViewportFramebuffer() override;
595 void resolveSamples() override;
596
597 QOpenGLContext *context;
598 QOpenGLFramebufferObject *fbo;
599 QOpenGLFramebufferObject *resolvedFbo;
600 QOffscreenSurface *surface;
601 bool initialized;
602 bool fakeHidden;
603 bool inBackingStorePaint;
604 bool hasBeenComposed;
605 bool flushPending;
606 QOpenGLPaintDevice *paintDevice;
607 QSurfaceFormat requestedFormat;
608 QOpenGLWidget::UpdateBehavior updateBehavior;
609 int requestedSamples;
610 bool inPaintGL;
611 GLenum textureFormat;
612 };
613
beginPaint()614 void QOpenGLWidgetPaintDevicePrivate::beginPaint()
615 {
616 // NB! autoFillBackground is and must be false by default. Otherwise we would clear on
617 // every QPainter begin() which is not desirable. This is only for legacy use cases,
618 // like using QOpenGLWidget as the viewport of a graphics view, that expect clearing
619 // with the palette's background color.
620 if (w->autoFillBackground()) {
621 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
622 if (w->format().hasAlpha()) {
623 f->glClearColor(0, 0, 0, 0);
624 } else {
625 QColor c = w->palette().brush(w->backgroundRole()).color();
626 float alpha = c.alphaF();
627 f->glClearColor(c.redF() * alpha, c.greenF() * alpha, c.blueF() * alpha, alpha);
628 }
629 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
630 }
631 }
632
endPaint()633 void QOpenGLWidgetPaintDevicePrivate::endPaint()
634 {
635 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(w));
636 if (!wd->initialized)
637 return;
638
639 if (!wd->inPaintGL)
640 QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = 0;
641 }
642
ensureActiveTarget()643 void QOpenGLWidgetPaintDevice::ensureActiveTarget()
644 {
645 QOpenGLWidgetPaintDevicePrivate *d = static_cast<QOpenGLWidgetPaintDevicePrivate *>(d_ptr.data());
646 QOpenGLWidgetPrivate *wd = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(d->w));
647 if (!wd->initialized)
648 return;
649
650 if (QOpenGLContext::currentContext() != wd->context)
651 d->w->makeCurrent();
652 else
653 wd->fbo->bind();
654
655 if (!wd->inPaintGL)
656 QOpenGLContextPrivate::get(wd->context)->defaultFboRedirect = wd->fbo->handle();
657
658 // When used as a viewport, drawing is done via opening a QPainter on the widget
659 // without going through paintEvent(). We will have to make sure a glFlush() is done
660 // before the texture is accessed also in this case.
661 wd->flushPending = true;
662 }
663
textureId() const664 GLuint QOpenGLWidgetPrivate::textureId() const
665 {
666 return resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0);
667 }
668
669 #ifndef GL_SRGB
670 #define GL_SRGB 0x8C40
671 #endif
672 #ifndef GL_SRGB8
673 #define GL_SRGB8 0x8C41
674 #endif
675 #ifndef GL_SRGB_ALPHA
676 #define GL_SRGB_ALPHA 0x8C42
677 #endif
678 #ifndef GL_SRGB8_ALPHA8
679 #define GL_SRGB8_ALPHA8 0x8C43
680 #endif
681
textureListFlags()682 QPlatformTextureList::Flags QOpenGLWidgetPrivate::textureListFlags()
683 {
684 QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags();
685 switch (textureFormat) {
686 case GL_SRGB:
687 case GL_SRGB8:
688 case GL_SRGB_ALPHA:
689 case GL_SRGB8_ALPHA8:
690 flags |= QPlatformTextureList::TextureIsSrgb;
691 break;
692 default:
693 break;
694 }
695 return flags;
696 }
697
reset()698 void QOpenGLWidgetPrivate::reset()
699 {
700 Q_Q(QOpenGLWidget);
701
702 // Destroy the OpenGL resources first. These need the context to be current.
703 if (initialized)
704 q->makeCurrent();
705
706 delete paintDevice;
707 paintDevice = nullptr;
708 delete fbo;
709 fbo = nullptr;
710 delete resolvedFbo;
711 resolvedFbo = nullptr;
712
713 if (initialized)
714 q->doneCurrent();
715
716 // Delete the context first, then the surface. Slots connected to
717 // the context's aboutToBeDestroyed() may still call makeCurrent()
718 // to perform some cleanup.
719 delete context;
720 context = nullptr;
721 delete surface;
722 surface = nullptr;
723 initialized = fakeHidden = inBackingStorePaint = false;
724 }
725
recreateFbo()726 void QOpenGLWidgetPrivate::recreateFbo()
727 {
728 Q_Q(QOpenGLWidget);
729
730 emit q->aboutToResize();
731
732 context->makeCurrent(surface);
733
734 delete fbo;
735 fbo = nullptr;
736 delete resolvedFbo;
737 resolvedFbo = nullptr;
738
739 int samples = requestedSamples;
740 QOpenGLExtensions *extfuncs = static_cast<QOpenGLExtensions *>(context->functions());
741 if (!extfuncs->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample))
742 samples = 0;
743
744 QOpenGLFramebufferObjectFormat format;
745 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
746 format.setSamples(samples);
747 if (textureFormat)
748 format.setInternalTextureFormat(textureFormat);
749
750 const QSize deviceSize = q->size() * q->devicePixelRatioF();
751 fbo = new QOpenGLFramebufferObject(deviceSize, format);
752 if (samples > 0)
753 resolvedFbo = new QOpenGLFramebufferObject(deviceSize);
754
755 textureFormat = fbo->format().internalTextureFormat();
756
757 fbo->bind();
758 context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
759 flushPending = true; // Make sure the FBO is initialized before use
760
761 paintDevice->setSize(deviceSize);
762 paintDevice->setDevicePixelRatio(q->devicePixelRatioF());
763
764 emit q->resized();
765 }
766
beginCompose()767 void QOpenGLWidgetPrivate::beginCompose()
768 {
769 Q_Q(QOpenGLWidget);
770 if (flushPending) {
771 flushPending = false;
772 q->makeCurrent();
773 static_cast<QOpenGLExtensions *>(context->functions())->flushShared();
774 }
775 hasBeenComposed = true;
776 emit q->aboutToCompose();
777 }
778
endCompose()779 void QOpenGLWidgetPrivate::endCompose()
780 {
781 Q_Q(QOpenGLWidget);
782 emit q->frameSwapped();
783 }
784
initialize()785 void QOpenGLWidgetPrivate::initialize()
786 {
787 Q_Q(QOpenGLWidget);
788 if (initialized)
789 return;
790
791 // If no global shared context get our toplevel's context with which we
792 // will share in order to make the texture usable by the underlying window's backingstore.
793 QWidget *tlw = q->window();
794 QOpenGLContext *shareContext = qt_gl_global_share_context();
795 if (!shareContext)
796 shareContext = get(tlw)->shareContext();
797 // If shareContext is null, showing content on-screen will not work.
798 // However, offscreen rendering and grabFramebuffer() will stay fully functional.
799
800 // Do not include the sample count. Requesting a multisampled context is not necessary
801 // since we render into an FBO, never to an actual surface. What's more, attempting to
802 // create a pbuffer with a multisampled config crashes certain implementations. Just
803 // avoid the entire hassle, the result is the same.
804 requestedSamples = requestedFormat.samples();
805 requestedFormat.setSamples(0);
806
807 QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext);
808 ctx->setFormat(requestedFormat);
809 if (shareContext) {
810 ctx->setShareContext(shareContext);
811 ctx->setScreen(shareContext->screen());
812 }
813 if (Q_UNLIKELY(!ctx->create())) {
814 qWarning("QOpenGLWidget: Failed to create context");
815 return;
816 }
817
818 // Propagate settings that make sense only for the tlw. Note that this only
819 // makes sense for properties that get picked up even after the native
820 // window is created.
821 if (tlw->windowHandle()) {
822 QSurfaceFormat tlwFormat = tlw->windowHandle()->format();
823 if (requestedFormat.swapInterval() != tlwFormat.swapInterval()) {
824 // Most platforms will pick up the changed swap interval on the next
825 // makeCurrent or swapBuffers.
826 tlwFormat.setSwapInterval(requestedFormat.swapInterval());
827 tlw->windowHandle()->setFormat(tlwFormat);
828 }
829 if (requestedFormat.swapBehavior() != tlwFormat.swapBehavior()) {
830 tlwFormat.setSwapBehavior(requestedFormat.swapBehavior());
831 tlw->windowHandle()->setFormat(tlwFormat);
832 }
833 }
834
835 // The top-level window's surface is not good enough since it causes way too
836 // much trouble with regards to the QSurfaceFormat for example. So just like
837 // in QQuickWidget, use a dedicated QOffscreenSurface.
838 surface = new QOffscreenSurface;
839 surface->setFormat(ctx->format());
840 surface->setScreen(ctx->screen());
841 surface->create();
842
843 if (Q_UNLIKELY(!ctx->makeCurrent(surface))) {
844 qWarning("QOpenGLWidget: Failed to make context current");
845 return;
846 }
847
848 paintDevice = new QOpenGLWidgetPaintDevice(q);
849 paintDevice->setSize(q->size() * q->devicePixelRatioF());
850 paintDevice->setDevicePixelRatio(q->devicePixelRatioF());
851
852 context = ctx.take();
853 initialized = true;
854
855 q->initializeGL();
856 }
857
resolveSamples()858 void QOpenGLWidgetPrivate::resolveSamples()
859 {
860 Q_Q(QOpenGLWidget);
861 if (resolvedFbo) {
862 q->makeCurrent();
863 QRect rect(QPoint(0, 0), fbo->size());
864 QOpenGLFramebufferObject::blitFramebuffer(resolvedFbo, rect, fbo, rect);
865 flushPending = true;
866 }
867 }
868
invokeUserPaint()869 void QOpenGLWidgetPrivate::invokeUserPaint()
870 {
871 Q_Q(QOpenGLWidget);
872
873 QOpenGLContext *ctx = QOpenGLContext::currentContext();
874 Q_ASSERT(ctx && fbo);
875
876 QOpenGLFunctions *f = ctx->functions();
877 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbo->handle();
878
879 f->glViewport(0, 0, q->width() * q->devicePixelRatioF(), q->height() * q->devicePixelRatioF());
880 inPaintGL = true;
881 q->paintGL();
882 inPaintGL = false;
883 flushPending = true;
884
885 QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
886 }
887
render()888 void QOpenGLWidgetPrivate::render()
889 {
890 Q_Q(QOpenGLWidget);
891
892 if (fakeHidden || !initialized)
893 return;
894
895 q->makeCurrent();
896
897 if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
898 invalidateFbo();
899 hasBeenComposed = false;
900 }
901
902 invokeUserPaint();
903 }
904
invalidateFbo()905 void QOpenGLWidgetPrivate::invalidateFbo()
906 {
907 QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
908 if (f->hasOpenGLExtension(QOpenGLExtensions::DiscardFramebuffer)) {
909 const int gl_color_attachment0 = 0x8CE0; // GL_COLOR_ATTACHMENT0
910 const int gl_depth_attachment = 0x8D00; // GL_DEPTH_ATTACHMENT
911 const int gl_stencil_attachment = 0x8D20; // GL_STENCIL_ATTACHMENT
912 #ifdef Q_OS_WASM
913 // webgl does not allow separate depth and stencil attachments
914 // QTBUG-69913
915 const int gl_depth_stencil_attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT
916
917 const GLenum attachments[] = {
918 gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment, gl_depth_stencil_attachment
919 };
920 #else
921 const GLenum attachments[] = {
922 gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment
923 };
924 #endif
925 f->glDiscardFramebufferEXT(GL_FRAMEBUFFER, sizeof attachments / sizeof *attachments, attachments);
926 } else {
927 f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
928 }
929 }
930
931 extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
932
grabFramebuffer()933 QImage QOpenGLWidgetPrivate::grabFramebuffer()
934 {
935 Q_Q(QOpenGLWidget);
936
937 initialize();
938 if (!initialized)
939 return QImage();
940
941 if (!fbo) // could be completely offscreen, without ever getting a resize event
942 recreateFbo();
943
944 if (!inPaintGL)
945 render();
946
947 if (resolvedFbo) {
948 resolveSamples();
949 resolvedFbo->bind();
950 } else {
951 q->makeCurrent();
952 }
953
954 const bool hasAlpha = q->format().hasAlpha();
955 QImage res = qt_gl_read_framebuffer(q->size() * q->devicePixelRatioF(), hasAlpha, hasAlpha);
956 res.setDevicePixelRatio(q->devicePixelRatioF());
957
958 // While we give no guarantees of what is going to be left bound, prefer the
959 // multisample fbo instead of the resolved one. Clients may continue to
960 // render straight after calling this function.
961 if (resolvedFbo)
962 q->makeCurrent();
963
964 return res;
965 }
966
initializeViewportFramebuffer()967 void QOpenGLWidgetPrivate::initializeViewportFramebuffer()
968 {
969 Q_Q(QOpenGLWidget);
970 // Legacy behavior for compatibility with QGLWidget when used as a graphics view
971 // viewport: enable clearing on each painter begin.
972 q->setAutoFillBackground(true);
973 }
974
resizeViewportFramebuffer()975 void QOpenGLWidgetPrivate::resizeViewportFramebuffer()
976 {
977 Q_Q(QOpenGLWidget);
978 if (!initialized)
979 return;
980
981 if (!fbo || q->size() * q->devicePixelRatioF() != fbo->size()) {
982 recreateFbo();
983 q->update();
984 }
985 }
986
987 /*!
988 Constructs a widget which is a child of \a parent, with widget flags set to \a f.
989 */
QOpenGLWidget(QWidget * parent,Qt::WindowFlags f)990 QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f)
991 : QWidget(*(new QOpenGLWidgetPrivate), parent, f)
992 {
993 Q_D(QOpenGLWidget);
994 if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)))
995 qWarning("QOpenGLWidget is not supported on this platform.");
996 else
997 d->setRenderToTexture();
998 }
999
1000 /*!
1001 Destroys the QOpenGLWidget instance, freeing its resources.
1002
1003 The QOpenGLWidget's context is made current in the destructor, allowing for
1004 safe destruction of any child object that may need to release OpenGL
1005 resources belonging to the context provided by this widget.
1006
1007 \warning if you have objects wrapping OpenGL resources (such as
1008 QOpenGLBuffer, QOpenGLShaderProgram, etc.) as members of a OpenGLWidget
1009 subclass, you may need to add a call to makeCurrent() in that subclass'
1010 destructor as well. Due to the rules of C++ object destruction, those objects
1011 will be destroyed \e{before} calling this function (but after that the
1012 destructor of the subclass has run), therefore making the OpenGL context
1013 current in this function happens too late for their safe disposal.
1014
1015 \sa makeCurrent
1016 */
~QOpenGLWidget()1017 QOpenGLWidget::~QOpenGLWidget()
1018 {
1019 Q_D(QOpenGLWidget);
1020 d->reset();
1021 }
1022
1023 /*!
1024 Sets this widget's update behavior to \a updateBehavior.
1025 \since 5.5
1026 */
setUpdateBehavior(UpdateBehavior updateBehavior)1027 void QOpenGLWidget::setUpdateBehavior(UpdateBehavior updateBehavior)
1028 {
1029 Q_D(QOpenGLWidget);
1030 d->updateBehavior = updateBehavior;
1031 }
1032
1033 /*!
1034 \return the update behavior of the widget.
1035 \since 5.5
1036 */
updateBehavior() const1037 QOpenGLWidget::UpdateBehavior QOpenGLWidget::updateBehavior() const
1038 {
1039 Q_D(const QOpenGLWidget);
1040 return d->updateBehavior;
1041 }
1042
1043 /*!
1044 Sets the requested surface \a format.
1045
1046 When the format is not explicitly set via this function, the format returned by
1047 QSurfaceFormat::defaultFormat() will be used. This means that when having multiple
1048 OpenGL widgets, individual calls to this function can be replaced by one single call to
1049 QSurfaceFormat::setDefaultFormat() before creating the first widget.
1050
1051 \note Requesting an alpha buffer via this function will not lead to the
1052 desired results when the intention is to make other widgets beneath visible.
1053 Instead, use Qt::WA_AlwaysStackOnTop to enable semi-transparent QOpenGLWidget
1054 instances with other widgets visible underneath. Keep in mind however that
1055 this breaks the stacking order, so it will no longer be possible to have
1056 other widgets on top of the QOpenGLWidget.
1057
1058 \sa format(), Qt::WA_AlwaysStackOnTop, QSurfaceFormat::setDefaultFormat()
1059 */
setFormat(const QSurfaceFormat & format)1060 void QOpenGLWidget::setFormat(const QSurfaceFormat &format)
1061 {
1062 Q_D(QOpenGLWidget);
1063 if (Q_UNLIKELY(d->initialized)) {
1064 qWarning("QOpenGLWidget: Already initialized, setting the format has no effect");
1065 return;
1066 }
1067
1068 d->requestedFormat = format;
1069 }
1070
1071 /*!
1072 Returns the context and surface format used by this widget and its toplevel
1073 window.
1074
1075 After the widget and its toplevel have both been created, resized and shown,
1076 this function will return the actual format of the context. This may differ
1077 from the requested format if the request could not be fulfilled by the
1078 platform. It is also possible to get larger color buffer sizes than
1079 requested.
1080
1081 When the widget's window and the related OpenGL resources are not yet
1082 initialized, the return value is the format that has been set via
1083 setFormat().
1084
1085 \sa setFormat(), context()
1086 */
format() const1087 QSurfaceFormat QOpenGLWidget::format() const
1088 {
1089 Q_D(const QOpenGLWidget);
1090 return d->initialized ? d->context->format() : d->requestedFormat;
1091 }
1092
1093 /*!
1094 Sets a custom internal texture format of \a texFormat.
1095
1096 When working with sRGB framebuffers, it will be necessary to specify a
1097 format like \c{GL_SRGB8_ALPHA8}. This can be achieved by calling this
1098 function.
1099
1100 \note This function has no effect if called after the widget has already
1101 been shown and thus it performed initialization.
1102
1103 \note This function will typically have to be used in combination with a
1104 QSurfaceFormat::setDefaultFormat() call that sets the color space to
1105 QSurfaceFormat::sRGBColorSpace.
1106
1107 \since 5.10
1108 */
setTextureFormat(GLenum texFormat)1109 void QOpenGLWidget::setTextureFormat(GLenum texFormat)
1110 {
1111 Q_D(QOpenGLWidget);
1112 if (Q_UNLIKELY(d->initialized)) {
1113 qWarning("QOpenGLWidget: Already initialized, setting the internal texture format has no effect");
1114 return;
1115 }
1116
1117 d->textureFormat = texFormat;
1118 }
1119
1120 /*!
1121 \return the active internal texture format if the widget has already
1122 initialized, the requested format if one was set but the widget has not yet
1123 been made visible, or \nullptr if setTextureFormat() was not called and the
1124 widget has not yet been made visible.
1125
1126 \since 5.10
1127 */
textureFormat() const1128 GLenum QOpenGLWidget::textureFormat() const
1129 {
1130 Q_D(const QOpenGLWidget);
1131 return d->textureFormat;
1132 }
1133
1134 /*!
1135 \return \e true if the widget and OpenGL resources, like the context, have
1136 been successfully initialized. Note that the return value is always false
1137 until the widget is shown.
1138 */
isValid() const1139 bool QOpenGLWidget::isValid() const
1140 {
1141 Q_D(const QOpenGLWidget);
1142 return d->initialized && d->context->isValid();
1143 }
1144
1145 /*!
1146 Prepares for rendering OpenGL content for this widget by making the
1147 corresponding context current and binding the framebuffer object in that
1148 context.
1149
1150 It is not necessary to call this function in most cases, because it
1151 is called automatically before invoking paintGL().
1152
1153 \sa context(), paintGL(), doneCurrent()
1154 */
makeCurrent()1155 void QOpenGLWidget::makeCurrent()
1156 {
1157 Q_D(QOpenGLWidget);
1158 if (!d->initialized)
1159 return;
1160
1161 d->context->makeCurrent(d->surface);
1162
1163 if (d->fbo) // there may not be one if we are in reset()
1164 d->fbo->bind();
1165 }
1166
1167 /*!
1168 Releases the context.
1169
1170 It is not necessary to call this function in most cases, since the
1171 widget will make sure the context is bound and released properly
1172 when invoking paintGL().
1173 */
doneCurrent()1174 void QOpenGLWidget::doneCurrent()
1175 {
1176 Q_D(QOpenGLWidget);
1177 if (!d->initialized)
1178 return;
1179
1180 d->context->doneCurrent();
1181 }
1182
1183 /*!
1184 \return The QOpenGLContext used by this widget or \c 0 if not yet initialized.
1185
1186 \note The context and the framebuffer object used by the widget changes when
1187 reparenting the widget via setParent().
1188
1189 \sa QOpenGLContext::setShareContext(), defaultFramebufferObject()
1190 */
context() const1191 QOpenGLContext *QOpenGLWidget::context() const
1192 {
1193 Q_D(const QOpenGLWidget);
1194 return d->context;
1195 }
1196
1197 /*!
1198 \return The framebuffer object handle or \c 0 if not yet initialized.
1199
1200 \note The framebuffer object belongs to the context returned by context()
1201 and may not be accessible from other contexts.
1202
1203 \note The context and the framebuffer object used by the widget changes when
1204 reparenting the widget via setParent(). In addition, the framebuffer object
1205 changes on each resize.
1206
1207 \sa context()
1208 */
defaultFramebufferObject() const1209 GLuint QOpenGLWidget::defaultFramebufferObject() const
1210 {
1211 Q_D(const QOpenGLWidget);
1212 return d->fbo ? d->fbo->handle() : 0;
1213 }
1214
1215 /*!
1216 This virtual function is called once before the first call to
1217 paintGL() or resizeGL(). Reimplement it in a subclass.
1218
1219 This function should set up any required OpenGL resources and state.
1220
1221 There is no need to call makeCurrent() because this has already been
1222 done when this function is called. Note however that the framebuffer
1223 is not yet available at this stage, so avoid issuing draw calls from
1224 here. Defer such calls to paintGL() instead.
1225
1226 \sa paintGL(), resizeGL()
1227 */
initializeGL()1228 void QOpenGLWidget::initializeGL()
1229 {
1230 }
1231
1232 /*!
1233 This virtual function is called whenever the widget has been
1234 resized. Reimplement it in a subclass. The new size is passed in
1235 \a w and \a h.
1236
1237 There is no need to call makeCurrent() because this has already been
1238 done when this function is called. Additionally, the framebuffer is
1239 also bound.
1240
1241 \sa initializeGL(), paintGL()
1242 */
resizeGL(int w,int h)1243 void QOpenGLWidget::resizeGL(int w, int h)
1244 {
1245 Q_UNUSED(w);
1246 Q_UNUSED(h);
1247 }
1248
1249 /*!
1250 This virtual function is called whenever the widget needs to be
1251 painted. Reimplement it in a subclass.
1252
1253 There is no need to call makeCurrent() because this has already
1254 been done when this function is called.
1255
1256 Before invoking this function, the context and the framebuffer are
1257 bound, and the viewport is set up by a call to glViewport(). No
1258 other state is set and no clearing or drawing is performed by the
1259 framework.
1260
1261 \sa initializeGL(), resizeGL()
1262 */
paintGL()1263 void QOpenGLWidget::paintGL()
1264 {
1265 }
1266
1267 /*!
1268 Handles resize events that are passed in the \a e event parameter.
1269 Calls the virtual function resizeGL().
1270
1271 \note Avoid overriding this function in derived classes. If that is not
1272 feasible, make sure that QOpenGLWidget's implementation is invoked
1273 too. Otherwise the underlying framebuffer object and related resources will
1274 not get resized properly and will lead to incorrect rendering.
1275 */
resizeEvent(QResizeEvent * e)1276 void QOpenGLWidget::resizeEvent(QResizeEvent *e)
1277 {
1278 Q_D(QOpenGLWidget);
1279
1280 if (e->size().isEmpty()) {
1281 d->fakeHidden = true;
1282 return;
1283 }
1284 d->fakeHidden = false;
1285
1286 d->initialize();
1287 if (!d->initialized)
1288 return;
1289
1290 d->recreateFbo();
1291 resizeGL(width(), height());
1292 d->sendPaintEvent(QRect(QPoint(0, 0), size()));
1293 }
1294
1295 /*!
1296 Handles paint events.
1297
1298 Calling QWidget::update() will lead to sending a paint event \a e,
1299 and thus invoking this function. (NB this is asynchronous and will
1300 happen at some point after returning from update()). This function
1301 will then, after some preparation, call the virtual paintGL() to
1302 update the contents of the QOpenGLWidget's framebuffer. The widget's
1303 top-level window will then composite the framebuffer's texture with
1304 the rest of the window.
1305 */
paintEvent(QPaintEvent * e)1306 void QOpenGLWidget::paintEvent(QPaintEvent *e)
1307 {
1308 Q_UNUSED(e);
1309 Q_D(QOpenGLWidget);
1310 if (!d->initialized)
1311 return;
1312
1313 if (updatesEnabled())
1314 d->render();
1315 }
1316
1317 /*!
1318 Renders and returns a 32-bit RGB image of the framebuffer.
1319
1320 \note This is a potentially expensive operation because it relies on glReadPixels()
1321 to read back the pixels. This may be slow and can stall the GPU pipeline.
1322 */
grabFramebuffer()1323 QImage QOpenGLWidget::grabFramebuffer()
1324 {
1325 Q_D(QOpenGLWidget);
1326 return d->grabFramebuffer();
1327 }
1328
1329 /*!
1330 \reimp
1331 */
metric(QPaintDevice::PaintDeviceMetric metric) const1332 int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
1333 {
1334 Q_D(const QOpenGLWidget);
1335 if (d->inBackingStorePaint)
1336 return QWidget::metric(metric);
1337
1338 auto window = d->windowHandle(QWidgetPrivate::WindowHandleMode::TopLevel);
1339 QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
1340
1341 const float dpmx = qt_defaultDpiX() * 100. / 2.54;
1342 const float dpmy = qt_defaultDpiY() * 100. / 2.54;
1343
1344 switch (metric) {
1345 case PdmWidth:
1346 return width();
1347 case PdmHeight:
1348 return height();
1349 case PdmDepth:
1350 return 32;
1351 case PdmWidthMM:
1352 if (screen)
1353 return width() * screen->physicalSize().width() / screen->geometry().width();
1354 else
1355 return width() * 1000 / dpmx;
1356 case PdmHeightMM:
1357 if (screen)
1358 return height() * screen->physicalSize().height() / screen->geometry().height();
1359 else
1360 return height() * 1000 / dpmy;
1361 case PdmNumColors:
1362 return 0;
1363 case PdmDpiX:
1364 if (screen)
1365 return qRound(screen->logicalDotsPerInchX());
1366 else
1367 return qRound(dpmx * 0.0254);
1368 case PdmDpiY:
1369 if (screen)
1370 return qRound(screen->logicalDotsPerInchY());
1371 else
1372 return qRound(dpmy * 0.0254);
1373 case PdmPhysicalDpiX:
1374 if (screen)
1375 return qRound(screen->physicalDotsPerInchX());
1376 else
1377 return qRound(dpmx * 0.0254);
1378 case PdmPhysicalDpiY:
1379 if (screen)
1380 return qRound(screen->physicalDotsPerInchY());
1381 else
1382 return qRound(dpmy * 0.0254);
1383 case PdmDevicePixelRatio:
1384 if (window)
1385 return int(window->devicePixelRatio());
1386 else
1387 return 1.0;
1388 case PdmDevicePixelRatioScaled:
1389 if (window)
1390 return int(window->devicePixelRatio() * devicePixelRatioFScale());
1391 else
1392 return int(devicePixelRatioFScale());
1393 default:
1394 qWarning("QOpenGLWidget::metric(): unknown metric %d", metric);
1395 return 0;
1396 }
1397 }
1398
1399 /*!
1400 \reimp
1401 */
redirected(QPoint * p) const1402 QPaintDevice *QOpenGLWidget::redirected(QPoint *p) const
1403 {
1404 Q_D(const QOpenGLWidget);
1405 if (d->inBackingStorePaint)
1406 return QWidget::redirected(p);
1407
1408 return d->paintDevice;
1409 }
1410
1411 /*!
1412 \reimp
1413 */
paintEngine() const1414 QPaintEngine *QOpenGLWidget::paintEngine() const
1415 {
1416 Q_D(const QOpenGLWidget);
1417 // QWidget needs to "punch a hole" into the backingstore. This needs the
1418 // normal paint engine and device, not the GL one. So in this mode, behave
1419 // like a normal widget.
1420 if (d->inBackingStorePaint)
1421 return QWidget::paintEngine();
1422
1423 if (!d->initialized)
1424 return nullptr;
1425
1426 return d->paintDevice->paintEngine();
1427 }
1428
1429 /*!
1430 \reimp
1431 */
event(QEvent * e)1432 bool QOpenGLWidget::event(QEvent *e)
1433 {
1434 Q_D(QOpenGLWidget);
1435 switch (e->type()) {
1436 case QEvent::WindowChangeInternal:
1437 if (QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
1438 break;
1439 if (d->initialized)
1440 d->reset();
1441 if (isHidden())
1442 break;
1443 Q_FALLTHROUGH();
1444 case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too
1445 if (d->initialized && window()->windowHandle()
1446 && d->context->shareContext() != QWidgetPrivate::get(window())->shareContext())
1447 {
1448 // Special case: did grabFramebuffer() for a hidden widget that then became visible.
1449 // Recreate all resources since the context now needs to share with the TLW's.
1450 if (!QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
1451 d->reset();
1452 }
1453 if (!d->initialized && !size().isEmpty() && window()->windowHandle()) {
1454 d->initialize();
1455 if (d->initialized)
1456 d->recreateFbo();
1457 }
1458 break;
1459 case QEvent::ScreenChangeInternal:
1460 if (d->initialized && d->paintDevice->devicePixelRatioF() != devicePixelRatioF())
1461 d->recreateFbo();
1462 break;
1463 default:
1464 break;
1465 }
1466 return QWidget::event(e);
1467 }
1468
1469 QT_END_NAMESPACE
1470
1471 #include "moc_qopenglwidget.cpp"
1472