Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qopenglwindow.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include <QtGui/QOpenGLFunctions>
7#include <QtGui/private/qpaintdevicewindow_p.h>
8#include <QtGui/private/qopenglextensions_p.h>
9#include <QtGui/private/qopenglcontext_p.h>
10#include <QtGui/QMatrix4x4>
11#include <QtGui/QOffscreenSurface>
12
13#include <QtOpenGL/private/qopenglframebufferobject_p.h>
14#include <QtOpenGL/QOpenGLFramebufferObject>
15#include <QtOpenGL/QOpenGLTextureBlitter>
16#include <QtOpenGL/QOpenGLPaintDevice>
17
19
20/*!
21 \class QOpenGLWindow
22 \inmodule QtOpenGL
23 \since 5.4
24 \brief The QOpenGLWindow class is a convenience subclass of QWindow to perform OpenGL painting.
25
26 QOpenGLWindow is an enhanced QWindow that allows easily creating windows that
27 perform OpenGL rendering using an API that is compatible with QOpenGLWidget
28 Unlike QOpenGLWidget, QOpenGLWindow has no dependency on the widgets module
29 and offers better performance.
30
31 A typical application will subclass QOpenGLWindow and reimplement the following
32 virtual functions:
33
34 \list
35
36 \li initializeGL() to perform OpenGL resource initialization
37
38 \li resizeGL() to set up the transformation matrices and other window size dependent resources
39
40 \li paintGL() to issue OpenGL commands or draw using QPainter
41
42 \endlist
43
44 To schedule a repaint, call the update() function. Note that this will not
45 immediately result in a call to paintGL(). Calling update() multiple times in
46 a row will not change the behavior in any way.
47
48 This is a slot so it can be connected to a \l QChronoTimer::timeout() signal to
49 perform animation. Note however that in the modern OpenGL world it is a much
50 better choice to rely on synchronization to the vertical refresh rate of the
51 display. See \l{QSurfaceFormat::setSwapInterval()}{setSwapInterval()} on a
52 description of the swap interval. With a swap interval of \c 1, which is the
53 case on most systems by default, the
54 \l{QOpenGLContext::swapBuffers()}{swapBuffers()} call, that is executed
55 internally by QOpenGLWindow after each repaint, will block and wait for
56 vsync. This means that whenever the swap is done, an update can be scheduled
57 again by calling update(), without relying on timers.
58
59 To request a specific configuration for the context, use setFormat()
60 like for any other QWindow. This allows, among others, requesting a
61 given OpenGL version and profile, or enabling depth and stencil
62 buffers.
63
64 \note It is up to the application to ensure depth and stencil buffers are
65 requested from the underlying windowing system interface. Without requesting
66 a non-zero depth buffer size there is no guarantee that a depth buffer will
67 be available, and as a result depth testing related OpenGL operations may fail
68 to function as expected.
69
70 Commonly used depth and stencil buffer size requests are 24 and 8,
71 respectively. For example, a QOpenGLWindow subclass could do this in its
72 constructor:
73
74 \code
75 QSurfaceFormat format;
76 format.setDepthBufferSize(24);
77 format.setStencilBufferSize(8);
78 setFormat(format);
79 \endcode
80
81 Unlike QWindow, QOpenGLWindow allows opening a painter on itself and perform
82 QPainter-based drawing.
83
84 QOpenGLWindow supports multiple update behaviors. The default,
85 \c NoPartialUpdate is equivalent to a regular, OpenGL-based QWindow. In
86 contrast, \c PartialUpdateBlit and \c PartialUpdateBlend are
87 more in line with QOpenGLWidget's way of working, where there is always an
88 extra, dedicated framebuffer object present. These modes allow, by
89 sacrificing some performance, redrawing only a smaller area on each paint and
90 having the rest of the content preserved from of the previous frame. This is
91 useful for applications than render incrementally using QPainter, because
92 this way they do not have to redraw the entire window content on each
93 paintGL() call.
94
95 Similarly to QOpenGLWidget, QOpenGLWindow supports the Qt::AA_ShareOpenGLContexts
96 attribute. When enabled, the OpenGL contexts of all QOpenGLWindow instances will share
97 with each other. This allows accessing each other's shareable OpenGL resources.
98
99 For more information on graphics in Qt, see \l {Graphics}.
100 */
101
102/*!
103 \enum QOpenGLWindow::UpdateBehavior
104
105 This enum describes the update strategy of the QOpenGLWindow.
106
107 \value NoPartialUpdate Indicates that the entire window surface will
108 redrawn on each update and so no additional framebuffers are needed.
109 This is the setting used in most cases and is equivalent to how drawing
110 directly via QWindow would function.
111
112 \value PartialUpdateBlit Indicates that the drawing performed in paintGL()
113 does not cover the entire window. In this case an extra framebuffer object
114 is created under the hood, and rendering performed in paintGL() will target
115 this framebuffer. This framebuffer is then blitted onto the window surface's
116 default framebuffer after each paint. This allows having QPainter-based drawing
117 code in paintGL() which only repaints a smaller area at a time, because, unlike
118 NoPartialUpdate, the previous content is preserved.
119
120 \value PartialUpdateBlend Similar to PartialUpdateBlit, but instead of using
121 framebuffer blits, the contents of the extra framebuffer is rendered by
122 drawing a textured quad with blending enabled. This, unlike PartialUpdateBlit,
123 allows alpha blended content and works even when the glBlitFramebuffer is
124 not available. Performance-wise this setting is likely to be somewhat slower
125 than PartialUpdateBlit.
126 */
127
128/*!
129 \fn void QOpenGLWindow::frameSwapped()
130
131 This signal is emitted after the potentially blocking
132 \l{QOpenGLContext::swapBuffers()}{buffer swap} has been done. Applications
133 that wish to continuously repaint synchronized to the vertical refresh,
134 should issue an update() upon this signal. This allows for a much smoother
135 experience compared to the traditional usage of timers.
136*/
137
138// GLES2 builds won't have these constants with the suffixless names
139#ifndef GL_READ_FRAMEBUFFER
140#define GL_READ_FRAMEBUFFER 0x8CA8
141#endif
142#ifndef GL_DRAW_FRAMEBUFFER
143#define GL_DRAW_FRAMEBUFFER 0x8CA9
144#endif
145
154
189
190QOpenGLWindowPrivate::~QOpenGLWindowPrivate()
191{
192}
193
195{
196 Q_Q(QOpenGLWindow);
197
198 if (context)
199 return;
200
201 if (!q->handle())
202 qWarning("Attempted to initialize QOpenGLWindow without a platform window");
203
204 context.reset(new QOpenGLContext);
205 context->setShareContext(shareContext);
206 context->setFormat(q->requestedFormat());
207 if (!context->create())
208 qWarning("QOpenGLWindow::beginPaint: Failed to create context");
209 if (!context->makeCurrent(q))
210 qWarning("QOpenGLWindow::beginPaint: Failed to make context current");
211
212 paintDevice.reset(new QOpenGLWindowPaintDevice(q));
213 if (updateBehavior == QOpenGLWindow::PartialUpdateBlit)
214 hasFboBlit = QOpenGLFramebufferObject::hasOpenGLFramebufferBlit();
215
216 q->initializeGL();
217}
218
219void QOpenGLWindowPrivate::beginPaint(const QRegion &region)
220{
221 Q_UNUSED(region);
222 Q_Q(QOpenGLWindow);
223
225 context->makeCurrent(q);
226
227 const int deviceWidth = q->width() * q->devicePixelRatio();
228 const int deviceHeight = q->height() * q->devicePixelRatio();
229 const QSize deviceSize(deviceWidth, deviceHeight);
230 if (updateBehavior > QOpenGLWindow::NoPartialUpdate) {
231 if (!fbo || fbo->size() != deviceSize) {
232 QOpenGLFramebufferObjectFormat fboFormat;
233 fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
234 const int samples = q->requestedFormat().samples();
235 if (samples > 0) {
236 if (updateBehavior != QOpenGLWindow::PartialUpdateBlend)
237 fboFormat.setSamples(samples);
238 else
239 qWarning("QOpenGLWindow: PartialUpdateBlend does not support multisampling");
240 }
241 fbo.reset(new QOpenGLFramebufferObject(deviceSize, fboFormat));
242 markWindowAsDirty();
243 }
244 } else {
245 markWindowAsDirty();
246 }
247
248 paintDevice->setSize(QSize(deviceWidth, deviceHeight));
249 paintDevice->setDevicePixelRatio(q->devicePixelRatio());
250 context->functions()->glViewport(0, 0, deviceWidth, deviceHeight);
251
252 context->functions()->glBindFramebuffer(GL_FRAMEBUFFER, context->defaultFramebufferObject());
253
254 q->paintUnderGL();
255
256 if (updateBehavior > QOpenGLWindow::NoPartialUpdate)
257 fbo->bind();
258}
259
261{
262 Q_Q(QOpenGLWindow);
263
264 if (updateBehavior > QOpenGLWindow::NoPartialUpdate)
265 fbo->release();
266
267 context->functions()->glBindFramebuffer(GL_FRAMEBUFFER, context->defaultFramebufferObject());
268
269 if (updateBehavior == QOpenGLWindow::PartialUpdateBlit && hasFboBlit) {
270 const int deviceWidth = q->width() * q->devicePixelRatio();
271 const int deviceHeight = q->height() * q->devicePixelRatio();
272 QOpenGLExtensions extensions(context.data());
273 extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle());
274 extensions.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, context->defaultFramebufferObject());
275 extensions.glBlitFramebuffer(0, 0, deviceWidth, deviceHeight,
276 0, 0, deviceWidth, deviceHeight,
277 GL_COLOR_BUFFER_BIT, GL_NEAREST);
278 } else if (updateBehavior > QOpenGLWindow::NoPartialUpdate) {
279 if (updateBehavior == QOpenGLWindow::PartialUpdateBlend) {
280 context->functions()->glEnable(GL_BLEND);
281 context->functions()->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
282 }
283 if (!blitter.isCreated())
284 blitter.create();
285
286 QRect windowRect(QPoint(0, 0), fbo->size());
287 QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(windowRect, windowRect);
288 blitter.bind();
289 blitter.blit(fbo->texture(), target, QOpenGLTextureBlitter::OriginBottomLeft);
290 blitter.release();
291
292 if (updateBehavior == QOpenGLWindow::PartialUpdateBlend)
293 context->functions()->glDisable(GL_BLEND);
294 }
295
296 q->paintOverGL();
297}
298
300{
301 if (updateBehavior > QOpenGLWindow::NoPartialUpdate)
302 fbo->bind();
303 else
304 QOpenGLFramebufferObject::bindDefault();
305}
306
307void QOpenGLWindowPrivate::flush(const QRegion &region)
308{
309 Q_UNUSED(region);
310 Q_Q(QOpenGLWindow);
311 context->swapBuffers(q);
312 emit q->frameSwapped();
313}
314
316{
317 QOpenGLWindowPrivate::get(m_window)->bindFBO();
318}
319
320/*!
321 Constructs a new QOpenGLWindow with the given \a parent and \a updateBehavior.
322
323 \sa QOpenGLWindow::UpdateBehavior
324 */
325QOpenGLWindow::QOpenGLWindow(QOpenGLWindow::UpdateBehavior updateBehavior, QWindow *parent)
326 : QPaintDeviceWindow(*(new QOpenGLWindowPrivate(nullptr, updateBehavior)), parent)
327{
328 setSurfaceType(QSurface::OpenGLSurface);
329}
330
331/*!
332 Constructs a new QOpenGLWindow with the given \a parent and \a updateBehavior. The QOpenGLWindow's context will share with \a shareContext.
333
334 \sa QOpenGLWindow::UpdateBehavior shareContext
335*/
336QOpenGLWindow::QOpenGLWindow(QOpenGLContext *shareContext, UpdateBehavior updateBehavior, QWindow *parent)
337 : QPaintDeviceWindow(*(new QOpenGLWindowPrivate(shareContext, updateBehavior)), parent)
338{
339 setSurfaceType(QSurface::OpenGLSurface);
340}
341
342/*!
343 Destroys the QOpenGLWindow instance, freeing its resources.
344
345 The OpenGLWindow's context is made current in the destructor, allowing for
346 safe destruction of any child object that may need to release OpenGL
347 resources belonging to the context provided by this window.
348
349 \warning if you have objects wrapping OpenGL resources (such as
350 QOpenGLBuffer, QOpenGLShaderProgram, etc.) as members of a QOpenGLWindow
351 subclass, you may need to add a call to makeCurrent() in that subclass'
352 destructor as well. Due to the rules of C++ object destruction, those objects
353 will be destroyed \e{before} calling this function (but after that the
354 destructor of the subclass has run), therefore making the OpenGL context
355 current in this function happens too late for their safe disposal.
356
357 \sa makeCurrent
358
359 \since 5.5
360*/
361QOpenGLWindow::~QOpenGLWindow()
362{
363 Q_D(QOpenGLWindow);
364
365 makeCurrent(); // this works even when the platformwindow is destroyed
366 if (isValid()) {
367 d->paintDevice.reset(nullptr);
368 d->fbo.reset(nullptr);
369 d->blitter.destroy();
370 }
371 doneCurrent();
372}
373
374/*!
375 \return the update behavior for this QOpenGLWindow.
376*/
377QOpenGLWindow::UpdateBehavior QOpenGLWindow::updateBehavior() const
378{
379 Q_D(const QOpenGLWindow);
380 return d->updateBehavior;
381}
382
383/*!
384 \return \c true if the window's OpenGL resources, like the context, have
385 been successfully initialized. Note that the return value is always \c false
386 until the window becomes exposed (shown).
387*/
388bool QOpenGLWindow::isValid() const
389{
390 Q_D(const QOpenGLWindow);
391 return d->context && d->context->isValid();
392}
393
394/*!
395 Prepares for rendering OpenGL content for this window by making the
396 corresponding context current and binding the framebuffer object, if there is
397 one, in that context.
398
399 It is not necessary to call this function in most cases, because it is called
400 automatically before invoking paintGL(). It is provided nonetheless to support
401 advanced, multi-threaded scenarios where a thread different than the GUI or main
402 thread may want to update the surface or framebuffer contents. See QOpenGLContext
403 for more information on threading related issues.
404
405 This function is suitable for calling also when the underlying platform window
406 is already destroyed. This means that it is safe to call this function from
407 a QOpenGLWindow subclass' destructor. If there is no native window anymore,
408 an offscreen surface is used instead. This ensures that OpenGL resource
409 cleanup operations in the destructor will always work, as long as
410 this function is called first.
411
412 \sa QOpenGLContext, context(), paintGL(), doneCurrent()
413 */
414void QOpenGLWindow::makeCurrent()
415{
416 Q_D(QOpenGLWindow);
417
418 if (!isValid())
419 return;
420
421 // The platform window may be destroyed at this stage and therefore
422 // makeCurrent() may not safely be called with 'this'.
423 if (handle()) {
424 d->context->makeCurrent(this);
425 } else {
426 if (!d->offscreenSurface) {
427 d->offscreenSurface.reset(new QOffscreenSurface(screen()));
428 d->offscreenSurface->setFormat(d->context->format());
429 d->offscreenSurface->create();
430 }
431 d->context->makeCurrent(d->offscreenSurface.data());
432 }
433
434 d->bindFBO();
435}
436
437/*!
438 Releases the context.
439
440 It is not necessary to call this function in most cases, since the widget
441 will make sure the context is bound and released properly when invoking
442 paintGL().
443
444 \sa makeCurrent()
445 */
446void QOpenGLWindow::doneCurrent()
447{
448 Q_D(QOpenGLWindow);
449
450 if (!isValid())
451 return;
452
453 d->context->doneCurrent();
454}
455
456/*!
457 \return The QOpenGLContext used by this window or \c 0 if not yet initialized.
458 */
459QOpenGLContext *QOpenGLWindow::context() const
460{
461 Q_D(const QOpenGLWindow);
462 return d->context.data();
463}
464
465/*!
466 \return The QOpenGLContext requested to be shared with this window's QOpenGLContext.
467*/
468QOpenGLContext *QOpenGLWindow::shareContext() const
469{
470 Q_D(const QOpenGLWindow);
471 return d->shareContext;
472}
473
474/*!
475 The framebuffer object handle used by this window.
476
477 When the update behavior is set to \c NoPartialUpdate, there is no separate
478 framebuffer object. In this case the returned value is the ID of the
479 default framebuffer.
480
481 Otherwise the value of the ID of the framebuffer object or \c 0 if not
482 yet initialized.
483 */
484GLuint QOpenGLWindow::defaultFramebufferObject() const
485{
486 Q_D(const QOpenGLWindow);
487 if (d->updateBehavior > NoPartialUpdate && d->fbo)
488 return d->fbo->handle();
489 else if (QOpenGLContext *ctx = QOpenGLContext::currentContext())
490 return ctx->defaultFramebufferObject();
491 else
492 return 0;
493}
494
495/*!
496 Returns a copy of the framebuffer.
497
498 \note This is a potentially expensive operation because it relies on
499 glReadPixels() to read back the pixels. This may be slow and can stall the
500 GPU pipeline.
501
502 \note When used together with update behavior \c NoPartialUpdate, the returned
503 image may not contain the desired content when called after the front and back
504 buffers have been swapped (unless preserved swap is enabled in the underlying
505 windowing system interface). In this mode the function reads from the back
506 buffer and the contents of that may not match the content on the screen (the
507 front buffer). In this case the only place where this function can safely be
508 used is paintGL() or paintOverGL().
509 */
510QImage QOpenGLWindow::grabFramebuffer()
511{
512 if (!isValid())
513 return QImage();
514
515 makeCurrent();
516
517 const bool hasAlpha = format().hasAlpha();
518 QImage img = qt_gl_read_framebuffer(size() * devicePixelRatio(), hasAlpha, hasAlpha);
519 img.setDevicePixelRatio(devicePixelRatio());
520 return img;
521}
522
523/*!
524 This virtual function is called once before the first call to paintGL() or
525 resizeGL(). Reimplement it in a subclass.
526
527 This function should set up any required OpenGL resources and state.
528
529 There is no need to call makeCurrent() because this has already been done
530 when this function is called. Note however that the framebuffer, in case
531 partial update mode is used, is not yet available at this stage, so avoid
532 issuing draw calls from here. Defer such calls to paintGL() instead.
533
534 \sa paintGL(), resizeGL()
535 */
536void QOpenGLWindow::initializeGL()
537{
538}
539
540/*!
541 This virtual function is called whenever the widget has been resized.
542 Reimplement it in a subclass. The new size is passed in \a w and \a h.
543
544 \note This is merely a convenience function in order to provide an API that is
545 compatible with QOpenGLWidget. Unlike with QOpenGLWidget, derived classes are
546 free to choose to override resizeEvent() instead of this function.
547
548 \note Avoid issuing OpenGL commands from this function as there may not be a
549 context current when it is invoked. If it cannot be avoided, call makeCurrent().
550
551 \note Scheduling updates from here is not necessary. The windowing systems
552 will send expose events that trigger an update automatically.
553
554 \sa initializeGL(), paintGL()
555 */
556void QOpenGLWindow::resizeGL(int w, int h)
557{
558 Q_UNUSED(w);
559 Q_UNUSED(h);
560}
561
562/*!
563 This virtual function is called whenever the window contents needs to be
564 painted. Reimplement it in a subclass.
565
566 There is no need to call makeCurrent() because this has already
567 been done when this function is called.
568
569 Before invoking this function, the context and the framebuffer, if there is
570 one, are bound, and the viewport is set up by a call to glViewport(). No
571 other state is set and no clearing or drawing is performed by the framework.
572
573 \note When using a partial update behavior, like \c PartialUpdateBlend, the
574 output of the previous paintGL() call is preserved and, after the additional
575 drawing performed in the current invocation of the function, the content is
576 blitted or blended over the content drawn directly to the window in
577 paintUnderGL().
578
579 \sa initializeGL(), resizeGL(), paintUnderGL(), paintOverGL(), UpdateBehavior
580 */
581void QOpenGLWindow::paintGL()
582{
583}
584
585/*!
586 The virtual function is called before each invocation of paintGL().
587
588 When the update mode is set to \c NoPartialUpdate, there is no difference
589 between this function and paintGL(), performing rendering in either of them
590 leads to the same result.
591
592 The difference becomes significant when using \c PartialUpdateBlend, where an
593 extra framebuffer object is used. There, paintGL() targets this additional
594 framebuffer object, which preserves its contents, while paintUnderGL() and
595 paintOverGL() target the default framebuffer, i.e. directly the window
596 surface, the contents of which is lost after each displayed frame.
597
598 \note Avoid relying on this function when the update behavior is
599 \c PartialUpdateBlit. This mode involves blitting the extra framebuffer used by
600 paintGL() onto the default framebuffer after each invocation of paintGL(),
601 thus overwriting all drawing generated in this function.
602
603 \sa paintGL(), paintOverGL(), UpdateBehavior
604 */
605void QOpenGLWindow::paintUnderGL()
606{
607}
608
609/*!
610 This virtual function is called after each invocation of paintGL().
611
612 When the update mode is set to NoPartialUpdate, there is no difference
613 between this function and paintGL(), performing rendering in either of them
614 leads to the same result.
615
616 Like paintUnderGL(), rendering in this function targets the default
617 framebuffer of the window, regardless of the update behavior. It gets called
618 after paintGL() has returned and the blit (PartialUpdateBlit) or quad drawing
619 (PartialUpdateBlend) has been done.
620
621 \sa paintGL(), paintUnderGL(), UpdateBehavior
622 */
623void QOpenGLWindow::paintOverGL()
624{
625}
626
627/*!
628 Paint \a event handler. Calls paintGL().
629
630 \sa paintGL()
631 */
632void QOpenGLWindow::paintEvent(QPaintEvent *event)
633{
634 Q_UNUSED(event);
635 paintGL();
636}
637
638/*!
639 Resize \a event handler. Calls resizeGL().
640
641 \sa resizeGL()
642 */
643void QOpenGLWindow::resizeEvent(QResizeEvent *event)
644{
645 Q_UNUSED(event);
646 Q_D(QOpenGLWindow);
647 d->initialize();
648 resizeGL(width(), height());
649}
650
651/*!
652 \internal
653 */
654int QOpenGLWindow::metric(PaintDeviceMetric metric) const
655{
656 Q_D(const QOpenGLWindow);
657
658 switch (metric) {
659 case PdmDepth:
660 if (d->paintDevice)
661 return d->paintDevice->depth();
662 break;
663 default:
664 break;
665 }
666 return QPaintDeviceWindow::metric(metric);
667}
668
669/*!
670 \internal
671 */
672QPaintDevice *QOpenGLWindow::redirected(QPoint *) const
673{
674 Q_D(const QOpenGLWindow);
675 if (QOpenGLContext::currentContext() == d->context.data())
676 return d->paintDevice.data();
677 return nullptr;
678}
679
680QT_END_NAMESPACE
681
682#include "moc_qopenglwindow.cpp"
QOpenGLWindowPaintDevice(QOpenGLWindow *window)
void ensureActiveTarget() override
This virtual method is provided as a callback to allow re-binding a target frame buffer object or con...
QScopedPointer< QOpenGLFramebufferObject > fbo
QScopedPointer< QOpenGLContext > context
QOpenGLTextureBlitter blitter
QScopedPointer< QOpenGLWindowPaintDevice > paintDevice
void endPaint() override
static QOpenGLWindowPrivate * get(QOpenGLWindow *w)
QScopedPointer< QOffscreenSurface > offscreenSurface
void flush(const QRegion &region) override
QOpenGLContext * shareContext
void beginPaint(const QRegion &region) override
Combined button and popup list for selecting options.
#define GL_DRAW_FRAMEBUFFER
#define GL_READ_FRAMEBUFFER