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.
333 The QOpenGLWindow's context will share with \a shareContext.
334
335 \sa QOpenGLWindow::UpdateBehavior, shareContext
336*/
337QOpenGLWindow::QOpenGLWindow(QOpenGLContext *shareContext, UpdateBehavior updateBehavior, QWindow *parent)
338 : QPaintDeviceWindow(*(new QOpenGLWindowPrivate(shareContext, updateBehavior)), parent)
339{
340 setSurfaceType(QSurface::OpenGLSurface);
341}
342
343/*!
344 Destroys the QOpenGLWindow instance, freeing its resources.
345
346 The OpenGLWindow's context is made current in the destructor, allowing for
347 safe destruction of any child object that may need to release OpenGL
348 resources belonging to the context provided by this window.
349
350 \warning if you have objects wrapping OpenGL resources (such as
351 QOpenGLBuffer, QOpenGLShaderProgram, etc.) as members of a QOpenGLWindow
352 subclass, you may need to add a call to makeCurrent() in that subclass'
353 destructor as well. Due to the rules of C++ object destruction, those objects
354 will be destroyed \e{before} calling this function (but after that the
355 destructor of the subclass has run), therefore making the OpenGL context
356 current in this function happens too late for their safe disposal.
357
358 \sa makeCurrent
359
360 \since 5.5
361*/
362QOpenGLWindow::~QOpenGLWindow()
363{
364 Q_D(QOpenGLWindow);
365
366 makeCurrent(); // this works even when the platformwindow is destroyed
367 if (isValid()) {
368 d->paintDevice.reset(nullptr);
369 d->fbo.reset(nullptr);
370 d->blitter.destroy();
371 }
372 doneCurrent();
373}
374
375/*!
376 \return the update behavior for this QOpenGLWindow.
377*/
378QOpenGLWindow::UpdateBehavior QOpenGLWindow::updateBehavior() const
379{
380 Q_D(const QOpenGLWindow);
381 return d->updateBehavior;
382}
383
384/*!
385 \return \c true if the window's OpenGL resources, like the context, have
386 been successfully initialized. Note that the return value is always \c false
387 until the window becomes exposed (shown).
388*/
389bool QOpenGLWindow::isValid() const
390{
391 Q_D(const QOpenGLWindow);
392 return d->context && d->context->isValid();
393}
394
395/*!
396 Prepares for rendering OpenGL content for this window by making the
397 corresponding context current and binding the framebuffer object, if there is
398 one, in that context.
399
400 It is not necessary to call this function in most cases, because it is called
401 automatically before invoking paintGL(). It is provided nonetheless to support
402 advanced, multi-threaded scenarios where a thread different than the GUI or main
403 thread may want to update the surface or framebuffer contents. See QOpenGLContext
404 for more information on threading related issues.
405
406 This function is suitable for calling also when the underlying platform window
407 is already destroyed. This means that it is safe to call this function from
408 a QOpenGLWindow subclass' destructor. If there is no native window anymore,
409 an offscreen surface is used instead. This ensures that OpenGL resource
410 cleanup operations in the destructor will always work, as long as
411 this function is called first.
412
413 \sa QOpenGLContext, context(), paintGL(), doneCurrent()
414 */
415void QOpenGLWindow::makeCurrent()
416{
417 Q_D(QOpenGLWindow);
418
419 if (!isValid())
420 return;
421
422 // The platform window may be destroyed at this stage and therefore
423 // makeCurrent() may not safely be called with 'this'.
424 if (handle()) {
425 d->context->makeCurrent(this);
426 } else {
427 if (!d->offscreenSurface) {
428 d->offscreenSurface.reset(new QOffscreenSurface(screen()));
429 d->offscreenSurface->setFormat(d->context->format());
430 d->offscreenSurface->create();
431 }
432 d->context->makeCurrent(d->offscreenSurface.data());
433 }
434
435 d->bindFBO();
436}
437
438/*!
439 Releases the context.
440
441 It is not necessary to call this function in most cases, since the widget
442 will make sure the context is bound and released properly when invoking
443 paintGL().
444
445 \sa makeCurrent()
446 */
447void QOpenGLWindow::doneCurrent()
448{
449 Q_D(QOpenGLWindow);
450
451 if (!isValid())
452 return;
453
454 d->context->doneCurrent();
455}
456
457/*!
458 \return The QOpenGLContext used by this window or \c 0 if not yet initialized.
459 */
460QOpenGLContext *QOpenGLWindow::context() const
461{
462 Q_D(const QOpenGLWindow);
463 return d->context.data();
464}
465
466/*!
467 \return The QOpenGLContext requested to be shared with this window's QOpenGLContext.
468*/
469QOpenGLContext *QOpenGLWindow::shareContext() const
470{
471 Q_D(const QOpenGLWindow);
472 return d->shareContext;
473}
474
475/*!
476 The framebuffer object handle used by this window.
477
478 When the update behavior is set to \c NoPartialUpdate, there is no separate
479 framebuffer object. In this case the returned value is the ID of the
480 default framebuffer.
481
482 Otherwise the value of the ID of the framebuffer object or \c 0 if not
483 yet initialized.
484 */
485GLuint QOpenGLWindow::defaultFramebufferObject() const
486{
487 Q_D(const QOpenGLWindow);
488 if (d->updateBehavior > NoPartialUpdate && d->fbo)
489 return d->fbo->handle();
490 else if (QOpenGLContext *ctx = QOpenGLContext::currentContext())
491 return ctx->defaultFramebufferObject();
492 else
493 return 0;
494}
495
496/*!
497 Returns a copy of the framebuffer.
498
499 \note This is a potentially expensive operation because it relies on
500 glReadPixels() to read back the pixels. This may be slow and can stall the
501 GPU pipeline.
502
503 \note When used together with update behavior \c NoPartialUpdate, the returned
504 image may not contain the desired content when called after the front and back
505 buffers have been swapped (unless preserved swap is enabled in the underlying
506 windowing system interface). In this mode the function reads from the back
507 buffer and the contents of that may not match the content on the screen (the
508 front buffer). In this case the only place where this function can safely be
509 used is paintGL() or paintOverGL().
510 */
511QImage QOpenGLWindow::grabFramebuffer()
512{
513 if (!isValid())
514 return QImage();
515
516 makeCurrent();
517
518 const bool hasAlpha = format().hasAlpha();
519 QImage img = qt_gl_read_framebuffer(size() * devicePixelRatio(), hasAlpha, hasAlpha);
520 img.setDevicePixelRatio(devicePixelRatio());
521 return img;
522}
523
524/*!
525 This virtual function is called once before the first call to paintGL() or
526 resizeGL(). Reimplement it in a subclass.
527
528 This function should set up any required OpenGL resources and state.
529
530 There is no need to call makeCurrent() because this has already been done
531 when this function is called. Note however that the framebuffer, in case
532 partial update mode is used, is not yet available at this stage, so avoid
533 issuing draw calls from here. Defer such calls to paintGL() instead.
534
535 \sa paintGL(), resizeGL()
536 */
537void QOpenGLWindow::initializeGL()
538{
539}
540
541/*!
542 This virtual function is called whenever the widget has been resized.
543 Reimplement it in a subclass. The new size is passed in \a w and \a h.
544
545 \note This is merely a convenience function in order to provide an API that is
546 compatible with QOpenGLWidget. Unlike with QOpenGLWidget, derived classes are
547 free to choose to override resizeEvent() instead of this function.
548
549 \note Avoid issuing OpenGL commands from this function as there may not be a
550 context current when it is invoked. If it cannot be avoided, call makeCurrent().
551
552 \note Scheduling updates from here is not necessary. The windowing systems
553 will send expose events that trigger an update automatically.
554
555 \sa initializeGL(), paintGL()
556 */
557void QOpenGLWindow::resizeGL(int w, int h)
558{
559 Q_UNUSED(w);
560 Q_UNUSED(h);
561}
562
563/*!
564 This virtual function is called whenever the window contents needs to be
565 painted. Reimplement it in a subclass.
566
567 There is no need to call makeCurrent() because this has already
568 been done when this function is called.
569
570 Before invoking this function, the context and the framebuffer, if there is
571 one, are bound, and the viewport is set up by a call to glViewport(). No
572 other state is set and no clearing or drawing is performed by the framework.
573
574 \note When using a partial update behavior, like \c PartialUpdateBlend, the
575 output of the previous paintGL() call is preserved and, after the additional
576 drawing performed in the current invocation of the function, the content is
577 blitted or blended over the content drawn directly to the window in
578 paintUnderGL().
579
580 \sa initializeGL(), resizeGL(), paintUnderGL(), paintOverGL(), UpdateBehavior
581 */
582void QOpenGLWindow::paintGL()
583{
584}
585
586/*!
587 The virtual function is called before each invocation of paintGL().
588
589 When the update mode is set to \c NoPartialUpdate, there is no difference
590 between this function and paintGL(), performing rendering in either of them
591 leads to the same result.
592
593 The difference becomes significant when using \c PartialUpdateBlend, where an
594 extra framebuffer object is used. There, paintGL() targets this additional
595 framebuffer object, which preserves its contents, while paintUnderGL() and
596 paintOverGL() target the default framebuffer, i.e. directly the window
597 surface, the contents of which is lost after each displayed frame.
598
599 \note Avoid relying on this function when the update behavior is
600 \c PartialUpdateBlit. This mode involves blitting the extra framebuffer used by
601 paintGL() onto the default framebuffer after each invocation of paintGL(),
602 thus overwriting all drawing generated in this function.
603
604 \sa paintGL(), paintOverGL(), UpdateBehavior
605 */
606void QOpenGLWindow::paintUnderGL()
607{
608}
609
610/*!
611 This virtual function is called after each invocation of paintGL().
612
613 When the update mode is set to NoPartialUpdate, there is no difference
614 between this function and paintGL(), performing rendering in either of them
615 leads to the same result.
616
617 Like paintUnderGL(), rendering in this function targets the default
618 framebuffer of the window, regardless of the update behavior. It gets called
619 after paintGL() has returned and the blit (PartialUpdateBlit) or quad drawing
620 (PartialUpdateBlend) has been done.
621
622 \sa paintGL(), paintUnderGL(), UpdateBehavior
623 */
624void QOpenGLWindow::paintOverGL()
625{
626}
627
628/*!
629 Paint \a event handler. Calls paintGL().
630
631 \sa paintGL()
632 */
633void QOpenGLWindow::paintEvent(QPaintEvent *event)
634{
635 Q_UNUSED(event);
636 paintGL();
637}
638
639/*!
640 Resize \a event handler. Calls resizeGL().
641
642 \sa resizeGL()
643 */
644void QOpenGLWindow::resizeEvent(QResizeEvent *event)
645{
646 Q_UNUSED(event);
647 Q_D(QOpenGLWindow);
648 d->initialize();
649 resizeGL(width(), height());
650}
651
652/*!
653 \internal
654 */
655int QOpenGLWindow::metric(PaintDeviceMetric metric) const
656{
657 Q_D(const QOpenGLWindow);
658
659 switch (metric) {
660 case PdmDepth:
661 if (d->paintDevice)
662 return d->paintDevice->depth();
663 break;
664 default:
665 break;
666 }
667 return QPaintDeviceWindow::metric(metric);
668}
669
670/*!
671 \internal
672 */
673QPaintDevice *QOpenGLWindow::redirected(QPoint *) const
674{
675 Q_D(const QOpenGLWindow);
676 if (QOpenGLContext::currentContext() == d->context.data())
677 return d->paintDevice.data();
678 return nullptr;
679}
680
681QT_END_NAMESPACE
682
683#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