1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qabstract3dgraph.h" |
5 | #include "qabstract3dgraph_p.h" |
6 | #include "abstract3dcontroller_p.h" |
7 | #include "qabstract3dinputhandler_p.h" |
8 | #include "q3dscene_p.h" |
9 | #include "qutils.h" |
10 | #include "utils_p.h" |
11 | |
12 | #include <QtGui/QGuiApplication> |
13 | #include <QtGui/QOpenGLContext> |
14 | #include <QtOpenGL/QOpenGLPaintDevice> |
15 | #include <QtGui/QPainter> |
16 | #include <QtOpenGL/QOpenGLFramebufferObject> |
17 | #include <QtGui/QOffscreenSurface> |
18 | #if defined(Q_OS_MACOS) |
19 | #include <qpa/qplatformnativeinterface.h> |
20 | #endif |
21 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | /*! |
25 | * \class QAbstract3DGraph |
26 | * \inmodule QtDataVisualization |
27 | * \brief The QAbstract3DGraph class provides a window and render loop for graphs. |
28 | * \since QtDataVisualization 1.0 |
29 | * |
30 | * This class subclasses a QWindow and provides render loop for graphs inheriting it. |
31 | * |
32 | * You should not need to use this class directly, but one of its subclasses instead. |
33 | * |
34 | * Anti-aliasing is turned on by default on C++, except in OpenGL ES2 |
35 | * environments, where anti-aliasing is not supported by Qt Data Visualization. |
36 | * To specify non-default anti-aliasing for a graph, give a custom surface format as |
37 | * a constructor parameter. You can use the convenience function \c qDefaultSurfaceFormat() |
38 | * to create the surface format object. |
39 | * |
40 | * \note QAbstract3DGraph sets window flag \c Qt::FramelessWindowHint on by default. If you want to display |
41 | * graph windows as standalone windows with regular window frame, clear this flag after constructing |
42 | * the graph. For example: |
43 | * |
44 | * \code |
45 | * Q3DBars *graphWindow = new Q3DBars; |
46 | * graphWindow->setFlags(graphWindow->flags() ^ Qt::FramelessWindowHint); |
47 | * \endcode |
48 | * |
49 | * \sa Q3DBars, Q3DScatter, Q3DSurface, {Qt Data Visualization C++ Classes} |
50 | */ |
51 | |
52 | /*! |
53 | \enum QAbstract3DGraph::SelectionFlag |
54 | |
55 | Item selection modes. Values of this enumeration can be combined with OR operator. |
56 | |
57 | \value SelectionNone |
58 | Selection mode disabled. |
59 | \value SelectionItem |
60 | Selection highlights a single item. |
61 | \value SelectionRow |
62 | Selection highlights a single row. |
63 | \value SelectionItemAndRow |
64 | Combination flag for highlighting both item and row with different colors. |
65 | \value SelectionColumn |
66 | Selection highlights a single column. |
67 | \value SelectionItemAndColumn |
68 | Combination flag for highlighting both item and column with different colors. |
69 | \value SelectionRowAndColumn |
70 | Combination flag for highlighting both row and column. |
71 | \value SelectionItemRowAndColumn |
72 | Combination flag for highlighting item, row, and column. |
73 | \value SelectionSlice |
74 | Setting this mode flag indicates that the graph should take care of the slice view handling |
75 | automatically. If you wish to control the slice view yourself via Q3DScene, do not set this |
76 | flag. When setting this mode flag, either \c SelectionRow or \c SelectionColumn must also |
77 | be set, but not both. Slicing is supported by Q3DBars and Q3DSurface only. |
78 | When this flag is set, slice mode is entered in the following situations: |
79 | \list |
80 | \li When selection is changed explicitly via series API to a visible item |
81 | \li When selection is changed by clicking on the graph |
82 | \li When the selection mode changes and the selected item is visible |
83 | \endlist |
84 | \value SelectionMultiSeries |
85 | Setting this mode means that items for all series at same position are highlighted, instead |
86 | of just the selected item. The actual selection in the other series doesn't change. |
87 | When setting this mode flag, one or more of the basic selection flags (\c {SelectionItem}, |
88 | \c {SelectionRow}, or \c SelectionColumn) must also be set. |
89 | Multi-series selection is not supported for Q3DScatter. |
90 | */ |
91 | |
92 | /*! |
93 | \enum QAbstract3DGraph::ShadowQuality |
94 | |
95 | Quality of shadows. |
96 | |
97 | \value ShadowQualityNone |
98 | Shadows are disabled. |
99 | \value ShadowQualityLow |
100 | Shadows are rendered in low quality. |
101 | \value ShadowQualityMedium |
102 | Shadows are rendered in medium quality. |
103 | \value ShadowQualityHigh |
104 | Shadows are rendered in high quality. |
105 | \value ShadowQualitySoftLow |
106 | Shadows are rendered in low quality with softened edges. |
107 | \value ShadowQualitySoftMedium |
108 | Shadows are rendered in medium quality with softened edges. |
109 | \value ShadowQualitySoftHigh |
110 | Shadows are rendered in high quality with softened edges. |
111 | */ |
112 | |
113 | /*! |
114 | \enum QAbstract3DGraph::ElementType |
115 | \since QtDataVisualization 1.1 |
116 | |
117 | Type of an element in the graph. |
118 | |
119 | \value ElementNone |
120 | No defined element. |
121 | \value ElementSeries |
122 | A series (that is, an item in a series). |
123 | \value ElementAxisXLabel |
124 | The x-axis label. |
125 | \value ElementAxisYLabel |
126 | The y-axis label. |
127 | \value ElementAxisZLabel |
128 | The z-axis label. |
129 | \value ElementCustomItem |
130 | A custom item. |
131 | */ |
132 | |
133 | /*! |
134 | \enum QAbstract3DGraph::OptimizationHint |
135 | \since Qt Data Visualization 1.1 |
136 | |
137 | The optimization hint for rendering. |
138 | |
139 | \value OptimizationDefault |
140 | Provides the full feature set at a reasonable performance. |
141 | \value OptimizationStatic |
142 | Optimizes the rendering of static data sets at the expense of some features. |
143 | */ |
144 | |
145 | /*! |
146 | * \internal |
147 | */ |
148 | QAbstract3DGraph::QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFormat *format, |
149 | QWindow *parent) |
150 | : QWindow(parent), |
151 | d_ptr(d) |
152 | { |
153 | qRegisterMetaType<QAbstract3DGraph::ShadowQuality>(typeName: "QAbstract3DGraph::ShadowQuality" ); |
154 | qRegisterMetaType<QAbstract3DGraph::ElementType>(typeName: "QAbstract3DGraph::ElementType" ); |
155 | |
156 | // Default to frameless window, as typically graphs are not toplevel |
157 | setFlags(flags() | Qt::FramelessWindowHint); |
158 | |
159 | QSurfaceFormat surfaceFormat; |
160 | if (format) { |
161 | surfaceFormat = *format; |
162 | // Make sure renderable type is correct |
163 | surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType); |
164 | } else { |
165 | surfaceFormat = qDefaultSurfaceFormat(antialias: true); |
166 | } |
167 | |
168 | d_ptr->m_context = new QOpenGLContext(this); |
169 | setSurfaceType(QWindow::OpenGLSurface); |
170 | setFormat(surfaceFormat); |
171 | |
172 | create(); |
173 | |
174 | d_ptr->m_context->setFormat(requestedFormat()); |
175 | d_ptr->m_context->create(); |
176 | bool makeSuccess = d_ptr->m_context->makeCurrent(surface: this); |
177 | |
178 | // If we fail to get context, just abort |
179 | if (!makeSuccess || !QOpenGLContext::currentContext()) |
180 | return; |
181 | |
182 | initializeOpenGLFunctions(); |
183 | |
184 | const GLubyte *shaderVersion = glGetString(GL_SHADING_LANGUAGE_VERSION); |
185 | #ifndef QT_NO_DEBUG |
186 | const GLubyte *openGLVersion = glGetString(GL_VERSION); |
187 | qDebug() << "OpenGL version:" << (const char *)openGLVersion; |
188 | qDebug() << "GLSL version:" << (const char *)shaderVersion; |
189 | #endif |
190 | |
191 | if (!Utils::isOpenGLES()) { |
192 | // If we have real OpenGL, GLSL version must be 1.2 or over. Quit if not. |
193 | QStringList splitversionstr = |
194 | QString::fromLatin1(ba: (const char *)shaderVersion).split(sep: QChar::fromLatin1(c: ' ')); |
195 | if (splitversionstr[0].toFloat() < 1.2) |
196 | qFatal(msg: "GLSL version must be 1.20 or higher. Try installing latest display drivers." ); |
197 | } |
198 | |
199 | d_ptr->m_initialized = true; |
200 | |
201 | d_ptr->renderLater(); |
202 | |
203 | #if defined(Q_OS_MACOS) |
204 | // Enable touch events for Mac touchpads |
205 | typedef void * (*EnableTouch)(QWindow*, bool); |
206 | EnableTouch enableTouch = |
207 | (EnableTouch)QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow" ); |
208 | if (enableTouch) |
209 | enableTouch(this, true); |
210 | #endif |
211 | } |
212 | |
213 | /*! |
214 | * Destroys QAbstract3DGraph. |
215 | */ |
216 | QAbstract3DGraph::~QAbstract3DGraph() |
217 | { |
218 | } |
219 | |
220 | /*! |
221 | * Adds the given \a inputHandler to the graph. The input handlers added via addInputHandler |
222 | * are not taken in to use directly. Only the ownership of the \a inputHandler is given to the graph. |
223 | * The \a inputHandler must not be null or already added to another graph. |
224 | * |
225 | * \sa releaseInputHandler(), setActiveInputHandler() |
226 | */ |
227 | void QAbstract3DGraph::addInputHandler(QAbstract3DInputHandler *inputHandler) |
228 | { |
229 | d_ptr->m_visualController->addInputHandler(inputHandler); |
230 | } |
231 | |
232 | /*! |
233 | * Releases the ownership of the \a inputHandler back to the caller, if it was added to this graph. |
234 | * If the released \a inputHandler is in use there will be no input handler active after this call. |
235 | * |
236 | * If the default input handler is released and added back later, it behaves as any other input handler would. |
237 | * |
238 | * \sa addInputHandler(), setActiveInputHandler() |
239 | */ |
240 | void QAbstract3DGraph::releaseInputHandler(QAbstract3DInputHandler *inputHandler) |
241 | { |
242 | d_ptr->m_visualController->releaseInputHandler(inputHandler); |
243 | } |
244 | |
245 | /*! |
246 | * \property QAbstract3DGraph::activeInputHandler |
247 | * |
248 | * \brief The active input handler used in the graph. |
249 | */ |
250 | |
251 | /*! |
252 | * Sets \a inputHandler as the active input handler used in the graph. |
253 | * Implicitly calls addInputHandler() to transfer ownership of \a inputHandler |
254 | * to this graph. |
255 | * |
256 | * If \a inputHandler is null, no input handler will be active after this call. |
257 | * |
258 | * \sa addInputHandler(), releaseInputHandler() |
259 | */ |
260 | void QAbstract3DGraph::setActiveInputHandler(QAbstract3DInputHandler *inputHandler) |
261 | { |
262 | d_ptr->m_visualController->setActiveInputHandler(inputHandler); |
263 | } |
264 | |
265 | QAbstract3DInputHandler *QAbstract3DGraph::activeInputHandler() const |
266 | { |
267 | return d_ptr->m_visualController->activeInputHandler(); |
268 | } |
269 | |
270 | /*! |
271 | * Returns the list of all added input handlers. |
272 | * |
273 | * \sa addInputHandler() |
274 | */ |
275 | QList<QAbstract3DInputHandler *> QAbstract3DGraph::inputHandlers() const |
276 | { |
277 | return d_ptr->m_visualController->inputHandlers(); |
278 | } |
279 | |
280 | /*! |
281 | * Adds the given \a theme to the graph. The themes added via addTheme are not taken in to use |
282 | * directly. Only the ownership of the theme is given to the graph. |
283 | * The \a theme must not be null or already added to another graph. |
284 | * |
285 | * \sa releaseTheme(), setActiveTheme() |
286 | */ |
287 | void QAbstract3DGraph::addTheme(Q3DTheme *theme) |
288 | { |
289 | d_ptr->m_visualController->addTheme(theme); |
290 | } |
291 | |
292 | /*! |
293 | * Releases the ownership of the \a theme back to the caller, if it was added to this graph. |
294 | * If the released \a theme is in use, a new default theme will be created and set active. |
295 | * |
296 | * If the default theme is released and added back later, it behaves as any other theme would. |
297 | * |
298 | * \sa addTheme(), setActiveTheme() |
299 | */ |
300 | void QAbstract3DGraph::releaseTheme(Q3DTheme *theme) |
301 | { |
302 | d_ptr->m_visualController->releaseTheme(theme); |
303 | } |
304 | |
305 | /*! |
306 | * \property QAbstract3DGraph::activeTheme |
307 | * |
308 | * \brief The active theme of the graph. |
309 | */ |
310 | |
311 | /*! |
312 | * Sets \a theme as the active theme to be used for the graph. Implicitly calls |
313 | * addTheme() to transfer the ownership of the theme to this graph. |
314 | * |
315 | * If \a theme is null, a temporary default theme is created. This temporary theme is destroyed |
316 | * if any theme is explicitly set later. |
317 | * Properties of the theme can be modified even after setting it, and the modifications take |
318 | * effect immediately. |
319 | */ |
320 | void QAbstract3DGraph::setActiveTheme(Q3DTheme *theme) |
321 | { |
322 | d_ptr->m_visualController->setActiveTheme(theme); |
323 | } |
324 | |
325 | |
326 | Q3DTheme *QAbstract3DGraph::activeTheme() const |
327 | { |
328 | return d_ptr->m_visualController->activeTheme(); |
329 | } |
330 | |
331 | /*! |
332 | * Returns the list of all added themes. |
333 | * |
334 | * \sa addTheme() |
335 | */ |
336 | QList<Q3DTheme *> QAbstract3DGraph::themes() const |
337 | { |
338 | return d_ptr->m_visualController->themes(); |
339 | } |
340 | |
341 | /*! |
342 | * \property QAbstract3DGraph::selectionMode |
343 | * |
344 | * \brief Item selection mode. |
345 | * |
346 | * A combination of SelectionFlags. By default, \c SelectionItem. |
347 | * Different graph types support different selection modes. |
348 | * |
349 | * \sa SelectionFlags |
350 | */ |
351 | void QAbstract3DGraph::setSelectionMode(SelectionFlags mode) |
352 | { |
353 | d_ptr->m_visualController->setSelectionMode(mode); |
354 | } |
355 | |
356 | QAbstract3DGraph::SelectionFlags QAbstract3DGraph::selectionMode() const |
357 | { |
358 | return d_ptr->m_visualController->selectionMode(); |
359 | } |
360 | |
361 | /*! |
362 | * \property QAbstract3DGraph::shadowQuality |
363 | * |
364 | * \brief The quality of the shadow. |
365 | * |
366 | * One of the ShadowQuality enum values. By default, \c ShadowQualityMedium. |
367 | * |
368 | * \note If setting the shadow quality to a certain level fails, the level is lowered |
369 | * until it is successfully set. The \c shadowQualityChanged signal is emitted each time |
370 | * a change is made. |
371 | * |
372 | * \sa ShadowQuality |
373 | */ |
374 | void QAbstract3DGraph::setShadowQuality(ShadowQuality quality) |
375 | { |
376 | d_ptr->m_visualController->setShadowQuality(quality); |
377 | } |
378 | |
379 | QAbstract3DGraph::ShadowQuality QAbstract3DGraph::shadowQuality() const |
380 | { |
381 | return d_ptr->m_visualController->shadowQuality(); |
382 | } |
383 | |
384 | /*! |
385 | * Returns \c true if shadows are supported with the current configuration. |
386 | * OpenGL ES2 configurations do not support shadows. |
387 | */ |
388 | bool QAbstract3DGraph::shadowsSupported() const |
389 | { |
390 | return d_ptr->m_visualController->shadowsSupported(); |
391 | } |
392 | |
393 | /*! |
394 | * \property QAbstract3DGraph::scene |
395 | * |
396 | * \brief The Q3DScene pointer that can be used to manipulate the scene and |
397 | * access the scene elements, such as the active camera. |
398 | * |
399 | * This property is read-only. |
400 | */ |
401 | Q3DScene *QAbstract3DGraph::scene() const |
402 | { |
403 | return d_ptr->m_visualController->scene(); |
404 | } |
405 | |
406 | /*! |
407 | * Clears selection from all attached series. |
408 | */ |
409 | void QAbstract3DGraph::clearSelection() |
410 | { |
411 | d_ptr->m_visualController->clearSelection(); |
412 | } |
413 | |
414 | /*! |
415 | * Returns whether the \a series has already been added to the graph. |
416 | * |
417 | * \since 6.3 |
418 | */ |
419 | bool QAbstract3DGraph::hasSeries(QAbstract3DSeries *series) const |
420 | { |
421 | return d_ptr->m_visualController->hasSeries(series); |
422 | } |
423 | |
424 | /*! |
425 | * Adds a QCustom3DItem \a item to the graph. Graph takes ownership of the added item. |
426 | * |
427 | * Returns the index to the added item if the add operation was successful, -1 |
428 | * if trying to add a null item, and the index of the item if trying to add an |
429 | * already added item. |
430 | * |
431 | * Items are rendered in the order they have been inserted. The rendering order needs to |
432 | * be taken into account when having solid and transparent items. |
433 | * |
434 | * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt(), customItems() |
435 | * |
436 | * \since QtDataVisualization 1.1 |
437 | */ |
438 | int QAbstract3DGraph::addCustomItem(QCustom3DItem *item) |
439 | { |
440 | return d_ptr->m_visualController->addCustomItem(item); |
441 | } |
442 | |
443 | /*! |
444 | * Removes all custom items. Deletes the resources allocated to them. |
445 | * |
446 | * \since QtDataVisualization 1.1 |
447 | */ |
448 | void QAbstract3DGraph::removeCustomItems() |
449 | { |
450 | d_ptr->m_visualController->deleteCustomItems(); |
451 | } |
452 | |
453 | /*! |
454 | * Removes the custom \a {item}. Deletes the resources allocated to it. |
455 | * |
456 | * \since QtDataVisualization 1.1 |
457 | */ |
458 | void QAbstract3DGraph::removeCustomItem(QCustom3DItem *item) |
459 | { |
460 | d_ptr->m_visualController->deleteCustomItem(item); |
461 | } |
462 | |
463 | /*! |
464 | * Removes all custom items at \a {position}. Deletes the resources allocated to them. |
465 | * |
466 | * \since QtDataVisualization 1.1 |
467 | */ |
468 | void QAbstract3DGraph::removeCustomItemAt(const QVector3D &position) |
469 | { |
470 | d_ptr->m_visualController->deleteCustomItem(position); |
471 | } |
472 | |
473 | /*! |
474 | * Gets ownership of given \a item back and removes the \a item from the graph. |
475 | * |
476 | * \since QtDataVisualization 1.1 |
477 | * |
478 | * \note If the same item is added back to the graph, the texture or the texture file needs to be |
479 | * re-set. |
480 | * |
481 | * \sa QCustom3DItem::setTextureImage(), QCustom3DItem::setTextureFile() |
482 | */ |
483 | void QAbstract3DGraph::releaseCustomItem(QCustom3DItem *item) |
484 | { |
485 | return d_ptr->m_visualController->releaseCustomItem(item); |
486 | } |
487 | |
488 | /*! |
489 | * Returns the list of all added custom items. |
490 | * \since QtDataVisualization 1.2 |
491 | * \sa addCustomItem() |
492 | */ |
493 | QList<QCustom3DItem *> QAbstract3DGraph::customItems() const |
494 | { |
495 | return d_ptr->m_visualController->customItems(); |
496 | } |
497 | |
498 | /*! |
499 | * Can be used to query the index of the selected label after receiving \c selectedElementChanged |
500 | * signal with any label type. Selection is valid until the next \c selectedElementChanged signal. |
501 | * |
502 | * Returns the index of the selected label, or -1. |
503 | * |
504 | * \since QtDataVisualization 1.1 |
505 | * |
506 | * \sa selectedElement |
507 | */ |
508 | int QAbstract3DGraph::selectedLabelIndex() const |
509 | { |
510 | return d_ptr->m_visualController->selectedLabelIndex(); |
511 | } |
512 | |
513 | /*! |
514 | * Can be used to get the selected axis after receiving \c selectedElementChanged signal with any label |
515 | * type. Selection is valid until the next \c selectedElementChanged signal. |
516 | * |
517 | * Returns the pointer to the selected axis, or null. |
518 | * |
519 | * \since QtDataVisualization 1.1 |
520 | * |
521 | * \sa selectedElement |
522 | */ |
523 | QAbstract3DAxis *QAbstract3DGraph::selectedAxis() const |
524 | { |
525 | return d_ptr->m_visualController->selectedAxis(); |
526 | } |
527 | |
528 | /*! |
529 | * Can be used to query the index of the selected custom item after receiving \c selectedElementChanged |
530 | * signal with QAbstract3DGraph::ElementCustomItem type. Selection is valid until the next |
531 | * \c selectedElementChanged signal. |
532 | * |
533 | * Returns the index of the selected custom item, or -1. |
534 | * |
535 | * \since QtDataVisualization 1.1 |
536 | * |
537 | * \sa selectedElement |
538 | */ |
539 | int QAbstract3DGraph::selectedCustomItemIndex() const |
540 | { |
541 | return d_ptr->m_visualController->selectedCustomItemIndex(); |
542 | } |
543 | |
544 | /*! |
545 | * Can be used to get the selected custom item after receiving \c selectedElementChanged signal with |
546 | * QAbstract3DGraph::ElementCustomItem type. Ownership of the item remains with the graph. |
547 | * Selection is valid until the next \c selectedElementChanged signal. |
548 | * |
549 | * Returns the pointer to the selected custom item, or null. |
550 | * |
551 | * \since QtDataVisualization 1.1 |
552 | * |
553 | * \sa selectedElement |
554 | */ |
555 | QCustom3DItem *QAbstract3DGraph::selectedCustomItem() const |
556 | { |
557 | return d_ptr->m_visualController->selectedCustomItem(); |
558 | } |
559 | |
560 | /*! |
561 | * \property QAbstract3DGraph::selectedElement |
562 | * |
563 | * \brief The element selected in the graph. |
564 | * |
565 | * This property can be used to query the selected element type. The type is |
566 | * valid until a new selection is made in the graph and the |
567 | * \c selectedElementChanged signal is emitted. |
568 | * |
569 | * The signal can be used for example for implementing custom input handlers, as |
570 | * demonstrated in the \l {Graph Gallery} example under \uicontrol {Scatter Graph} tab. |
571 | * |
572 | * \sa selectedLabelIndex(), selectedAxis(), selectedCustomItemIndex(), selectedCustomItem(), |
573 | * Q3DBars::selectedSeries(), Q3DScatter::selectedSeries(), Q3DSurface::selectedSeries(), |
574 | * Q3DScene::setSelectionQueryPosition() |
575 | * |
576 | * \since QtDataVisualization 1.1 |
577 | */ |
578 | QAbstract3DGraph::ElementType QAbstract3DGraph::selectedElement() const |
579 | { |
580 | return d_ptr->m_visualController->selectedElement(); |
581 | } |
582 | |
583 | /*! |
584 | * Renders current frame to an image of \a imageSize. Default size is the window size. Image is |
585 | * rendered with antialiasing level given in \a msaaSamples. Default level is \c{0}. |
586 | * |
587 | * \since QtDataVisualization 1.1 |
588 | * |
589 | * Returns the rendered image. |
590 | * |
591 | * \note OpenGL ES2 does not support anitialiasing, so \a msaaSamples is always forced to \c{0}. |
592 | */ |
593 | QImage QAbstract3DGraph::renderToImage(int msaaSamples, const QSize &imageSize) |
594 | { |
595 | QSize renderSize = imageSize; |
596 | if (renderSize.isEmpty()) |
597 | renderSize = size(); |
598 | return d_ptr->renderToImage(msaaSamples, imageSize: renderSize); |
599 | } |
600 | |
601 | /*! |
602 | * \property QAbstract3DGraph::measureFps |
603 | * \since QtDataVisualization 1.1 |
604 | * |
605 | * \brief Whether rendering is done continuously instead of on demand. |
606 | * |
607 | * If \c {true}, rendering is continuous and the value of the currentFps |
608 | * property is updated. Defaults to \c{false}. |
609 | * |
610 | * \sa currentFps |
611 | */ |
612 | void QAbstract3DGraph::setMeasureFps(bool enable) |
613 | { |
614 | d_ptr->m_visualController->setMeasureFps(enable); |
615 | } |
616 | |
617 | bool QAbstract3DGraph::measureFps() const |
618 | { |
619 | return d_ptr->m_visualController->measureFps(); |
620 | } |
621 | |
622 | /*! |
623 | * \property QAbstract3DGraph::currentFps |
624 | * \since QtDataVisualization 1.1 |
625 | * |
626 | * \brief The rendering results for the last second. |
627 | * |
628 | * The results are stored in this read-only property when FPS measuring is |
629 | * enabled. It takes at least a second before this value is updated after |
630 | * measuring is activated. |
631 | * |
632 | * \sa measureFps |
633 | */ |
634 | qreal QAbstract3DGraph::currentFps() const |
635 | { |
636 | return d_ptr->m_visualController->currentFps(); |
637 | } |
638 | |
639 | /*! |
640 | * \property QAbstract3DGraph::orthoProjection |
641 | * \since QtDataVisualization 1.1 |
642 | * |
643 | * \brief Whether orthographic projection is used for displaying the graph. |
644 | * |
645 | * Defaults to \c{false}. |
646 | * \note Shadows will be disabled when set to \c{true}. |
647 | * |
648 | * \sa QAbstract3DAxis::labelAutoRotation, Q3DCamera::cameraPreset |
649 | */ |
650 | void QAbstract3DGraph::setOrthoProjection(bool enable) |
651 | { |
652 | d_ptr->m_visualController->setOrthoProjection(enable); |
653 | } |
654 | |
655 | bool QAbstract3DGraph::isOrthoProjection() const |
656 | { |
657 | return d_ptr->m_visualController->isOrthoProjection(); |
658 | } |
659 | |
660 | /*! |
661 | * \property QAbstract3DGraph::aspectRatio |
662 | * \since QtDataVisualization 1.1 |
663 | * |
664 | * \brief The ratio of the graph scaling between the longest axis on the |
665 | * horizontal plane and the y-axis. |
666 | * |
667 | * Defaults to \c{2.0}. |
668 | * |
669 | * \note Has no effect on Q3DBars. |
670 | * |
671 | * \sa horizontalAspectRatio |
672 | */ |
673 | void QAbstract3DGraph::setAspectRatio(qreal ratio) |
674 | { |
675 | d_ptr->m_visualController->setAspectRatio(ratio); |
676 | } |
677 | |
678 | qreal QAbstract3DGraph::aspectRatio() const |
679 | { |
680 | return d_ptr->m_visualController->aspectRatio(); |
681 | } |
682 | |
683 | /*! |
684 | * \property QAbstract3DGraph::optimizationHints |
685 | * |
686 | * \brief Whether the default or static mode is used for rendering optimization. |
687 | * |
688 | * The default mode provides the full feature set at a reasonable level of |
689 | * performance. The static mode optimizes graph rendering and is ideal for |
690 | * large non-changing data sets. It is slower with dynamic data changes and item rotations. |
691 | * Selection is not optimized, so using the static mode with massive data sets is not advisable. |
692 | * Static optimization works only on scatter graphs. |
693 | * Defaults to \l{OptimizationDefault}. |
694 | * |
695 | * \note On some environments, large graphs using static optimization may not render, because |
696 | * all of the items are rendered using a single draw call, and different graphics drivers |
697 | * support different maximum vertice counts per call. |
698 | * This is mostly an issue on 32bit and OpenGL ES2 platforms. |
699 | * To work around this issue, choose an item mesh with a low vertex count or use |
700 | * the point mesh. |
701 | * |
702 | * \sa QAbstract3DSeries::mesh |
703 | */ |
704 | void QAbstract3DGraph::setOptimizationHints(OptimizationHints hints) |
705 | { |
706 | d_ptr->m_visualController->setOptimizationHints(hints); |
707 | } |
708 | |
709 | QAbstract3DGraph::OptimizationHints QAbstract3DGraph::optimizationHints() const |
710 | { |
711 | return d_ptr->m_visualController->optimizationHints(); |
712 | } |
713 | |
714 | /*! |
715 | * \property QAbstract3DGraph::polar |
716 | * \since QtDataVisualization 1.2 |
717 | * |
718 | * \brief Whether horizontal axes are changed into polar axes. |
719 | * |
720 | * If \c {true}, the x-axis becomes the angular axis and the z-axis becomes the |
721 | * radial axis. |
722 | * Polar mode is not available for bar graphs. |
723 | * |
724 | * Defaults to \c{false}. |
725 | * |
726 | * \sa orthoProjection, radialLabelOffset |
727 | */ |
728 | void QAbstract3DGraph::setPolar(bool enable) |
729 | { |
730 | d_ptr->m_visualController->setPolar(enable); |
731 | } |
732 | |
733 | bool QAbstract3DGraph::isPolar() const |
734 | { |
735 | return d_ptr->m_visualController->isPolar(); |
736 | } |
737 | |
738 | /*! |
739 | * \property QAbstract3DGraph::radialLabelOffset |
740 | * \since QtDataVisualization 1.2 |
741 | * |
742 | * \brief The normalized horizontal offset for the axis labels of the radial |
743 | * polar axis. |
744 | * |
745 | * The value \c 0.0 indicates that the labels should be drawn next to the 0-angle |
746 | * angular axis grid line. The value \c 1.0 indicates that the labels are drawn |
747 | * in their usual place at the edge of the graph background. Defaults to \c 1.0. |
748 | * |
749 | * This property is ignored if the \l polar property value is \c{false}. |
750 | * |
751 | * \sa polar |
752 | */ |
753 | void QAbstract3DGraph::setRadialLabelOffset(float offset) |
754 | { |
755 | d_ptr->m_visualController->setRadialLabelOffset(offset); |
756 | } |
757 | |
758 | float QAbstract3DGraph::radialLabelOffset() const |
759 | { |
760 | return d_ptr->m_visualController->radialLabelOffset(); |
761 | } |
762 | |
763 | /*! |
764 | * \property QAbstract3DGraph::horizontalAspectRatio |
765 | * \since QtDataVisualization 1.2 |
766 | * |
767 | * \brief The ratio of the graph scaling between the x-axis and z-axis. |
768 | * |
769 | * The value of \c 0.0 indicates automatic scaling according to axis ranges. |
770 | * Defaults to \c{0.0}. |
771 | * |
772 | * Has no effect on Q3DBars, which handles scaling on the horizontal plane via |
773 | * the \l{Q3DBars::barThickness}{barThickness} and \l{Q3DBars::barSpacing}{barSpacing} properties. |
774 | * Polar graphs also ignore this property. |
775 | * |
776 | * \sa aspectRatio, polar, Q3DBars::barThickness, Q3DBars::barSpacing |
777 | */ |
778 | void QAbstract3DGraph::setHorizontalAspectRatio(qreal ratio) |
779 | { |
780 | d_ptr->m_visualController->setHorizontalAspectRatio(ratio); |
781 | } |
782 | |
783 | qreal QAbstract3DGraph::horizontalAspectRatio() const |
784 | { |
785 | return d_ptr->m_visualController->horizontalAspectRatio(); |
786 | } |
787 | |
788 | /*! |
789 | * \property QAbstract3DGraph::reflection |
790 | * \since QtDataVisualization 1.2 |
791 | * |
792 | * \brief Whether floor reflections are on or off. |
793 | * |
794 | * Defaults to \c{false}. |
795 | * |
796 | * Affects only Q3DBars. However, in Q3DBars graphs holding both positive and |
797 | * negative values, reflections are not supported for custom items that |
798 | * intersect the floor plane. In that case, reflections should be turned off |
799 | * to avoid incorrect rendering. |
800 | * |
801 | * If using a custom surface format, the stencil buffer needs to be defined |
802 | * (QSurfaceFormat::setStencilBufferSize()) for reflections to work. |
803 | * |
804 | * \sa reflectivity |
805 | */ |
806 | void QAbstract3DGraph::setReflection(bool enable) |
807 | { |
808 | d_ptr->m_visualController->setReflection(enable); |
809 | } |
810 | |
811 | bool QAbstract3DGraph::isReflection() const |
812 | { |
813 | return d_ptr->m_visualController->reflection(); |
814 | } |
815 | |
816 | /*! |
817 | * \property QAbstract3DGraph::reflectivity |
818 | * \since QtDataVisualization 1.2 |
819 | * |
820 | * \brief Floor reflectivity. |
821 | * |
822 | * Larger numbers make the floor more reflective. The valid range is \c{[0...1]}. |
823 | * Defaults to \c{0.5}. |
824 | * |
825 | * \note Affects only Q3DBars. |
826 | * |
827 | * \sa reflection |
828 | */ |
829 | void QAbstract3DGraph::setReflectivity(qreal reflectivity) |
830 | { |
831 | d_ptr->m_visualController->setReflectivity(reflectivity); |
832 | } |
833 | |
834 | qreal QAbstract3DGraph::reflectivity() const |
835 | { |
836 | return d_ptr->m_visualController->reflectivity(); |
837 | } |
838 | |
839 | /*! |
840 | * \property QAbstract3DGraph::locale |
841 | * \since QtDataVisualization 1.2 |
842 | * |
843 | * \brief The locale used for formatting various numeric labels. |
844 | * |
845 | * Defaults to the \c{"C"} locale. |
846 | * |
847 | * \sa QValue3DAxis::labelFormat |
848 | */ |
849 | void QAbstract3DGraph::setLocale(const QLocale &locale) |
850 | { |
851 | d_ptr->m_visualController->setLocale(locale); |
852 | } |
853 | |
854 | QLocale QAbstract3DGraph::locale() const |
855 | { |
856 | return d_ptr->m_visualController->locale(); |
857 | } |
858 | |
859 | /*! |
860 | * \property QAbstract3DGraph::queriedGraphPosition |
861 | * \since QtDataVisualization 1.2 |
862 | * |
863 | * \brief The latest queried graph position values along each axis. |
864 | * |
865 | * This read-only property contains the results from |
866 | * Q3DScene::graphPositionQuery. The values are normalized to the range \c{[-1, 1]}. |
867 | * If the queried position was outside the graph bounds, the values |
868 | * will not reflect the real position, but will instead indicate an undefined position outside |
869 | * the range \c{[-1, 1]}. The value will be undefined until a query is made. |
870 | * |
871 | * There is no single correct 3D coordinate to match a particular screen position, so to be |
872 | * consistent, the queries are always done against the inner sides of an invisible box surrounding |
873 | * the graph. |
874 | * |
875 | * \note Bar graphs only allow querying graph position at the graph floor level, |
876 | * so the y-value is always zero for bar graphs and the valid queries can be only made at |
877 | * screen positions that contain the floor of the graph. |
878 | * |
879 | * \sa Q3DScene::graphPositionQuery |
880 | */ |
881 | QVector3D QAbstract3DGraph::queriedGraphPosition() const |
882 | { |
883 | return d_ptr->m_visualController->queriedGraphPosition(); |
884 | } |
885 | |
886 | /*! |
887 | * \property QAbstract3DGraph::margin |
888 | * \since QtDataVisualization 1.2 |
889 | * |
890 | * \brief The absolute value used for the space left between the edge of the |
891 | * plottable graph area and the edge of the graph background. |
892 | * |
893 | * If the margin value is negative, the margins are determined automatically and can vary according |
894 | * to the size of the items in the series and the type of the graph. |
895 | * The value is interpreted as a fraction of the y-axis range if the graph |
896 | * aspect ratios have not beed changed from the default values. |
897 | * Defaults to \c{-1.0}. |
898 | * |
899 | * \note Setting a smaller margin for a scatter graph than the automatically |
900 | * determined margin can cause the scatter items at the edges of the graph to |
901 | * overlap with the graph background. |
902 | * |
903 | * \note On scatter and surface graphs, if the margin is small in comparison to the axis label |
904 | * size, the positions of the edge labels of the axes are adjusted to avoid overlap with |
905 | * the edge labels of the neighboring axes. |
906 | */ |
907 | void QAbstract3DGraph::setMargin(qreal margin) |
908 | { |
909 | d_ptr->m_visualController->setMargin(margin); |
910 | } |
911 | |
912 | qreal QAbstract3DGraph::margin() const |
913 | { |
914 | return d_ptr->m_visualController->margin(); |
915 | } |
916 | |
917 | /*! |
918 | * Returns \c{true} if the OpenGL context of the graph has been successfully initialized. |
919 | * Trying to use a graph when the context initialization has failed typically results in a crash. |
920 | * A common reason for a context initialization failure is lack of sufficient platform support |
921 | * for OpenGL. |
922 | */ |
923 | bool QAbstract3DGraph::hasContext() const |
924 | { |
925 | if (d_ptr->m_initialized) |
926 | return true; |
927 | else |
928 | return false; |
929 | } |
930 | |
931 | /*! |
932 | * \internal |
933 | */ |
934 | bool QAbstract3DGraph::event(QEvent *event) |
935 | { |
936 | switch (event->type()) { |
937 | case QEvent::UpdateRequest: |
938 | d_ptr->renderNow(); |
939 | return true; |
940 | case QEvent::TouchBegin: |
941 | case QEvent::TouchCancel: |
942 | case QEvent::TouchUpdate: |
943 | case QEvent::TouchEnd: |
944 | d_ptr->m_visualController->touchEvent(event: static_cast<QTouchEvent *>(event)); |
945 | return true; |
946 | default: |
947 | return QWindow::event(event); |
948 | } |
949 | } |
950 | |
951 | /*! |
952 | * \internal |
953 | */ |
954 | void QAbstract3DGraph::resizeEvent(QResizeEvent *event) |
955 | { |
956 | Q_UNUSED(event); |
957 | |
958 | if (d_ptr->m_visualController) { |
959 | Q3DScene *scene = d_ptr->m_visualController->scene(); |
960 | scene->d_ptr->setWindowSize(QSize(width(), height())); |
961 | scene->d_ptr->setViewport(QRect(0, 0, width(), height())); |
962 | } |
963 | } |
964 | |
965 | /*! |
966 | * \internal |
967 | */ |
968 | void QAbstract3DGraph::exposeEvent(QExposeEvent *event) |
969 | { |
970 | Q_UNUSED(event); |
971 | |
972 | if (isExposed()) |
973 | d_ptr->renderNow(); |
974 | } |
975 | |
976 | /*! |
977 | * \internal |
978 | */ |
979 | void QAbstract3DGraph::mouseDoubleClickEvent(QMouseEvent *event) |
980 | { |
981 | d_ptr->m_visualController->mouseDoubleClickEvent(event); |
982 | } |
983 | |
984 | /*! |
985 | * \internal |
986 | */ |
987 | void QAbstract3DGraph::touchEvent(QTouchEvent *event) |
988 | { |
989 | d_ptr->m_visualController->touchEvent(event); |
990 | } |
991 | |
992 | /*! |
993 | * \internal |
994 | */ |
995 | void QAbstract3DGraph::mousePressEvent(QMouseEvent *event) |
996 | { |
997 | d_ptr->m_visualController->mousePressEvent(event, mousePos: event->pos()); |
998 | } |
999 | |
1000 | /*! |
1001 | * \internal |
1002 | */ |
1003 | void QAbstract3DGraph::mouseReleaseEvent(QMouseEvent *event) |
1004 | { |
1005 | d_ptr->m_visualController->mouseReleaseEvent(event, mousePos: event->pos()); |
1006 | } |
1007 | |
1008 | /*! |
1009 | * \internal |
1010 | */ |
1011 | void QAbstract3DGraph::mouseMoveEvent(QMouseEvent *event) |
1012 | { |
1013 | d_ptr->m_visualController->mouseMoveEvent(event, mousePos: event->pos()); |
1014 | } |
1015 | |
1016 | #if QT_CONFIG(wheelevent) |
1017 | /*! |
1018 | * \internal |
1019 | */ |
1020 | void QAbstract3DGraph::wheelEvent(QWheelEvent *event) |
1021 | { |
1022 | d_ptr->m_visualController->wheelEvent(event); |
1023 | } |
1024 | #endif |
1025 | |
1026 | QAbstract3DGraphPrivate::QAbstract3DGraphPrivate(QAbstract3DGraph *q) |
1027 | : QObject(0), |
1028 | q_ptr(q), |
1029 | m_updatePending(false), |
1030 | m_visualController(0), |
1031 | m_devicePixelRatio(1.f), |
1032 | m_offscreenSurface(0), |
1033 | m_initialized(false) |
1034 | { |
1035 | } |
1036 | |
1037 | QAbstract3DGraphPrivate::~QAbstract3DGraphPrivate() |
1038 | { |
1039 | if (m_offscreenSurface) { |
1040 | m_offscreenSurface->destroy(); |
1041 | delete m_offscreenSurface; |
1042 | } |
1043 | if (m_context) |
1044 | m_context->makeCurrent(surface: q_ptr); |
1045 | |
1046 | delete m_visualController; |
1047 | } |
1048 | |
1049 | void QAbstract3DGraphPrivate::setVisualController(Abstract3DController *controller) |
1050 | { |
1051 | m_visualController = controller; |
1052 | |
1053 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::activeInputHandlerChanged, context: q_ptr, |
1054 | slot: &QAbstract3DGraph::activeInputHandlerChanged); |
1055 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::activeThemeChanged, context: q_ptr, |
1056 | slot: &QAbstract3DGraph::activeThemeChanged); |
1057 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::selectionModeChanged, context: q_ptr, |
1058 | slot: &QAbstract3DGraph::selectionModeChanged); |
1059 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::shadowQualityChanged, context: q_ptr, |
1060 | slot: &QAbstract3DGraph::shadowQualityChanged); |
1061 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::optimizationHintsChanged, context: q_ptr, |
1062 | slot: &QAbstract3DGraph::optimizationHintsChanged); |
1063 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::elementSelected, context: q_ptr, |
1064 | slot: &QAbstract3DGraph::selectedElementChanged); |
1065 | |
1066 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::needRender, context: this, |
1067 | slot: &QAbstract3DGraphPrivate::renderLater); |
1068 | |
1069 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::axisXChanged, context: this, |
1070 | slot: &QAbstract3DGraphPrivate::handleAxisXChanged); |
1071 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::axisYChanged, context: this, |
1072 | slot: &QAbstract3DGraphPrivate::handleAxisYChanged); |
1073 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::axisZChanged, context: this, |
1074 | slot: &QAbstract3DGraphPrivate::handleAxisZChanged); |
1075 | |
1076 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::measureFpsChanged, context: q_ptr, |
1077 | slot: &QAbstract3DGraph::measureFpsChanged); |
1078 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::currentFpsChanged, context: q_ptr, |
1079 | slot: &QAbstract3DGraph::currentFpsChanged); |
1080 | |
1081 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::orthoProjectionChanged, context: q_ptr, |
1082 | slot: &QAbstract3DGraph::orthoProjectionChanged); |
1083 | |
1084 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::aspectRatioChanged, context: q_ptr, |
1085 | slot: &QAbstract3DGraph::aspectRatioChanged); |
1086 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::polarChanged, context: q_ptr, |
1087 | slot: &QAbstract3DGraph::polarChanged); |
1088 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::radialLabelOffsetChanged, context: q_ptr, |
1089 | slot: &QAbstract3DGraph::radialLabelOffsetChanged); |
1090 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::horizontalAspectRatioChanged, context: q_ptr, |
1091 | slot: &QAbstract3DGraph::horizontalAspectRatioChanged); |
1092 | |
1093 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::reflectionChanged, context: q_ptr, |
1094 | slot: &QAbstract3DGraph::reflectionChanged); |
1095 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::reflectivityChanged, context: q_ptr, |
1096 | slot: &QAbstract3DGraph::reflectivityChanged); |
1097 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::localeChanged, context: q_ptr, |
1098 | slot: &QAbstract3DGraph::localeChanged); |
1099 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::queriedGraphPositionChanged, context: q_ptr, |
1100 | slot: &QAbstract3DGraph::queriedGraphPositionChanged); |
1101 | QObject::connect(sender: m_visualController, signal: &Abstract3DController::marginChanged, context: q_ptr, |
1102 | slot: &QAbstract3DGraph::marginChanged); |
1103 | } |
1104 | |
1105 | void QAbstract3DGraphPrivate::handleDevicePixelRatioChange() |
1106 | { |
1107 | if (q_ptr->devicePixelRatio() == m_devicePixelRatio || !m_visualController) |
1108 | return; |
1109 | |
1110 | m_devicePixelRatio = q_ptr->devicePixelRatio(); |
1111 | m_visualController->scene()->setDevicePixelRatio(m_devicePixelRatio); |
1112 | } |
1113 | |
1114 | void QAbstract3DGraphPrivate::render() |
1115 | { |
1116 | handleDevicePixelRatioChange(); |
1117 | m_visualController->synchDataToRenderer(); |
1118 | m_visualController->render(); |
1119 | } |
1120 | |
1121 | void QAbstract3DGraphPrivate::renderLater() |
1122 | { |
1123 | if (!m_updatePending) { |
1124 | m_updatePending = true; |
1125 | QCoreApplication::postEvent(receiver: q_ptr, event: new QEvent(QEvent::UpdateRequest)); |
1126 | } |
1127 | } |
1128 | |
1129 | void QAbstract3DGraphPrivate::renderNow() |
1130 | { |
1131 | if (!q_ptr->isExposed()) |
1132 | return; |
1133 | |
1134 | m_updatePending = false; |
1135 | |
1136 | m_context->makeCurrent(surface: q_ptr); |
1137 | |
1138 | render(); |
1139 | |
1140 | m_context->swapBuffers(surface: q_ptr); |
1141 | } |
1142 | |
1143 | QImage QAbstract3DGraphPrivate::renderToImage(int msaaSamples, const QSize &imageSize) |
1144 | { |
1145 | QImage image; |
1146 | QOpenGLFramebufferObject *fbo; |
1147 | QOpenGLFramebufferObjectFormat fboFormat; |
1148 | if (!m_offscreenSurface) { |
1149 | // Create an offscreen surface for rendering to images without rendering on screen |
1150 | m_offscreenSurface = new QOffscreenSurface(q_ptr->screen()); |
1151 | m_offscreenSurface->setFormat(q_ptr->requestedFormat()); |
1152 | m_offscreenSurface->create(); |
1153 | } |
1154 | // Render the wanted frame offscreen |
1155 | m_context->makeCurrent(surface: m_offscreenSurface); |
1156 | fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
1157 | if (!Utils::isOpenGLES()) { |
1158 | fboFormat.setInternalTextureFormat(GL_RGB); |
1159 | fboFormat.setSamples(msaaSamples); |
1160 | } |
1161 | fbo = new QOpenGLFramebufferObject(imageSize, fboFormat); |
1162 | if (fbo->isValid()) { |
1163 | QRect originalViewport = m_visualController->m_scene->viewport(); |
1164 | m_visualController->m_scene->d_ptr->setWindowSize(imageSize); |
1165 | m_visualController->m_scene->d_ptr->setViewport(QRect(0, 0, |
1166 | imageSize.width(), |
1167 | imageSize.height())); |
1168 | m_visualController->synchDataToRenderer(); |
1169 | fbo->bind(); |
1170 | m_visualController->requestRender(fbo); |
1171 | image = fbo->toImage(); |
1172 | fbo->release(); |
1173 | m_visualController->m_scene->d_ptr->setWindowSize(originalViewport.size()); |
1174 | m_visualController->m_scene->d_ptr->setViewport(originalViewport); |
1175 | } |
1176 | delete fbo; |
1177 | m_context->makeCurrent(surface: q_ptr); |
1178 | |
1179 | return image; |
1180 | } |
1181 | |
1182 | QT_END_NAMESPACE |
1183 | |