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 | |
4 | #include "qsgrenderer_p.h" |
5 | #include "qsgnodeupdater_p.h" |
6 | #include <private/qquickprofiler_p.h> |
7 | #include <qtquick_tracepoints_p.h> |
8 | |
9 | #include <QtCore/QElapsedTimer> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | static QElapsedTimer frameTimer; |
14 | static qint64 preprocessTime; |
15 | static qint64 updatePassTime; |
16 | |
17 | Q_TRACE_POINT(qtquick, QSG_preprocess_entry) |
18 | Q_TRACE_POINT(qtquick, QSG_preprocess_exit) |
19 | Q_TRACE_POINT(qtquick, QSG_update_entry) |
20 | Q_TRACE_POINT(qtquick, QSG_update_exit) |
21 | Q_TRACE_POINT(qtquick, QSG_renderScene_entry) |
22 | Q_TRACE_POINT(qtquick, QSG_renderScene_exit) |
23 | |
24 | int qt_sg_envInt(const char *name, int defaultValue) |
25 | { |
26 | if (Q_LIKELY(!qEnvironmentVariableIsSet(name))) |
27 | return defaultValue; |
28 | bool ok = false; |
29 | int value = qEnvironmentVariableIntValue(varName: name, ok: &ok); |
30 | return ok ? value : defaultValue; |
31 | } |
32 | |
33 | /*! |
34 | \class QSGRenderer |
35 | \brief The renderer class is the abstract baseclass used for rendering the |
36 | QML scene graph. |
37 | |
38 | The renderer is not tied to any particular surface. It expects a context to |
39 | be current and will render into that surface according to how the device rect, |
40 | viewport rect and projection transformation are set up. |
41 | |
42 | Rendering is a sequence of steps initiated by calling renderScene(). This will |
43 | effectively draw the scene graph starting at the root node. The QSGNode::preprocess() |
44 | function will be called for all the nodes in the graph, followed by an update |
45 | pass which updates all matrices, opacity, clip states and similar in the graph. |
46 | Because the update pass is called after preprocess, it is safe to modify the graph |
47 | during preprocess. To run a custom update pass over the graph, install a custom |
48 | QSGNodeUpdater using setNodeUpdater(). Once all the graphs dirty states are updated, |
49 | the virtual render() function is called. |
50 | |
51 | The render() function is implemented by QSGRenderer subclasses to render the graph |
52 | in the most optimal way for a given hardware. |
53 | |
54 | The renderer can make use of stencil, depth and color buffers in addition to the |
55 | scissor rect. |
56 | |
57 | \internal |
58 | */ |
59 | |
60 | |
61 | QSGRenderer::QSGRenderer(QSGRenderContext *context) |
62 | : m_current_opacity(1) |
63 | , m_current_determinant(1) |
64 | , m_device_pixel_ratio(1) |
65 | , m_context(context) |
66 | , m_current_uniform_data(nullptr) |
67 | , m_current_resource_update_batch(nullptr) |
68 | , m_rhi(nullptr) |
69 | , m_node_updater(nullptr) |
70 | , m_changed_emitted(false) |
71 | , m_is_rendering(false) |
72 | , m_is_preprocessing(false) |
73 | { |
74 | m_current_projection_matrix.resize(sz: 1); |
75 | m_current_projection_matrix_native_ndc.resize(sz: 1); |
76 | } |
77 | |
78 | |
79 | QSGRenderer::~QSGRenderer() |
80 | { |
81 | setRootNode(nullptr); |
82 | delete m_node_updater; |
83 | } |
84 | |
85 | /*! |
86 | Returns the node updater that this renderer uses to update states in the |
87 | scene graph. |
88 | |
89 | If no updater is specified a default one is constructed. |
90 | */ |
91 | |
92 | QSGNodeUpdater *QSGRenderer::nodeUpdater() const |
93 | { |
94 | if (!m_node_updater) |
95 | const_cast<QSGRenderer *>(this)->m_node_updater = new QSGNodeUpdater(); |
96 | return m_node_updater; |
97 | } |
98 | |
99 | |
100 | /*! |
101 | Sets the node updater that this renderer uses to update states in the |
102 | scene graph. |
103 | |
104 | This will delete and override any existing node updater |
105 | */ |
106 | void QSGRenderer::setNodeUpdater(QSGNodeUpdater *updater) |
107 | { |
108 | if (m_node_updater) |
109 | delete m_node_updater; |
110 | m_node_updater = updater; |
111 | } |
112 | |
113 | bool QSGRenderer::isMirrored() const |
114 | { |
115 | QMatrix4x4 matrix = projectionMatrix(index: 0); |
116 | // Mirrored relative to the usual Qt coordinate system with origin in the top left corner. |
117 | return matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0) > 0; |
118 | } |
119 | |
120 | void QSGRenderer::renderScene() |
121 | { |
122 | if (!rootNode()) |
123 | return; |
124 | |
125 | Q_TRACE_SCOPE(QSG_renderScene); |
126 | m_is_rendering = true; |
127 | |
128 | bool profileFrames = QSG_LOG_TIME_RENDERER().isDebugEnabled(); |
129 | if (profileFrames) |
130 | frameTimer.start(); |
131 | Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRendererFrame); |
132 | |
133 | // The QML Profiler architecture is extremely fragile: we have to record a |
134 | // hardcoded number of data points for each event, otherwise the view will |
135 | // show weird things in Creator. So record a dummy Binding data point, even |
136 | // though it is meaningless for our purposes. |
137 | Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRendererFrame, |
138 | QQuickProfiler::SceneGraphRendererBinding); |
139 | |
140 | qint64 renderTime = 0; |
141 | |
142 | preprocess(); |
143 | |
144 | Q_TRACE(QSG_render_entry); |
145 | render(); |
146 | if (profileFrames) |
147 | renderTime = frameTimer.nsecsElapsed(); |
148 | Q_TRACE(QSG_render_exit); |
149 | Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRendererFrame, |
150 | QQuickProfiler::SceneGraphRendererRender); |
151 | |
152 | m_is_rendering = false; |
153 | m_changed_emitted = false; |
154 | |
155 | qCDebug(QSG_LOG_TIME_RENDERER, |
156 | "time in renderer: total=%dms, preprocess=%d, updates=%d, rendering=%d", |
157 | int(renderTime / 1000000), |
158 | int(preprocessTime / 1000000), |
159 | int((updatePassTime - preprocessTime) / 1000000), |
160 | int((renderTime - updatePassTime) / 1000000)); |
161 | } |
162 | |
163 | void QSGRenderer::prepareSceneInline() |
164 | { |
165 | if (!rootNode()) |
166 | return; |
167 | |
168 | Q_ASSERT(!m_is_rendering); |
169 | m_is_rendering = true; |
170 | |
171 | preprocess(); |
172 | |
173 | prepareInline(); |
174 | } |
175 | |
176 | void QSGRenderer::renderSceneInline() |
177 | { |
178 | Q_ASSERT(m_is_rendering); |
179 | |
180 | renderInline(); |
181 | |
182 | m_is_rendering = false; |
183 | m_changed_emitted = false; |
184 | } |
185 | |
186 | /*! |
187 | Updates internal data structures and emits the sceneGraphChanged() signal. |
188 | |
189 | If \a flags contains the QSGNode::DirtyNodeRemoved flag, the node might be |
190 | in the process of being destroyed. It is then not safe to downcast the node |
191 | pointer. |
192 | */ |
193 | |
194 | void QSGRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state) |
195 | { |
196 | if (state & QSGNode::DirtyNodeAdded) |
197 | addNodesToPreprocess(node); |
198 | if (state & QSGNode::DirtyNodeRemoved) |
199 | removeNodesToPreprocess(node); |
200 | if (state & QSGNode::DirtyUsePreprocess) { |
201 | if (node->flags() & QSGNode::UsePreprocess) |
202 | m_nodes_to_preprocess.insert(value: node); |
203 | else |
204 | m_nodes_to_preprocess.remove(value: node); |
205 | } |
206 | |
207 | if (!m_changed_emitted && !m_is_rendering) { |
208 | // Premature overoptimization to avoid excessive signal emissions |
209 | m_changed_emitted = true; |
210 | emit sceneGraphChanged(); |
211 | } |
212 | } |
213 | |
214 | void QSGRenderer::preprocess() |
215 | { |
216 | Q_TRACE(QSG_preprocess_entry); |
217 | |
218 | m_is_preprocessing = true; |
219 | |
220 | QSGRootNode *root = rootNode(); |
221 | Q_ASSERT(root); |
222 | |
223 | // We need to take a copy here, in case any of the preprocess calls deletes a node that |
224 | // is in the preprocess list and thus, changes the m_nodes_to_preprocess behind our backs |
225 | // For the default case, when this does not happen, the cost is negligible. |
226 | QSet<QSGNode *> items = m_nodes_to_preprocess; |
227 | |
228 | m_context->preprocess(); |
229 | |
230 | for (QSet<QSGNode *>::const_iterator it = items.constBegin(); |
231 | it != items.constEnd(); ++it) { |
232 | QSGNode *n = *it; |
233 | |
234 | // If we are currently preprocessing, check this node hasn't been |
235 | // deleted or something. we don't want a use-after-free! |
236 | if (m_nodes_dont_preprocess.contains(value: n)) // skip |
237 | continue; |
238 | if (!nodeUpdater()->isNodeBlocked(n, root)) { |
239 | n->preprocess(); |
240 | } |
241 | } |
242 | |
243 | bool profileFrames = QSG_LOG_TIME_RENDERER().isDebugEnabled(); |
244 | if (profileFrames) |
245 | preprocessTime = frameTimer.nsecsElapsed(); |
246 | Q_TRACE(QSG_preprocess_exit); |
247 | Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRendererFrame, |
248 | QQuickProfiler::SceneGraphRendererPreprocess); |
249 | Q_TRACE(QSG_update_entry); |
250 | |
251 | nodeUpdater()->updateStates(n: root); |
252 | |
253 | if (profileFrames) |
254 | updatePassTime = frameTimer.nsecsElapsed(); |
255 | Q_TRACE(QSG_update_exit); |
256 | Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRendererFrame, |
257 | QQuickProfiler::SceneGraphRendererUpdate); |
258 | |
259 | m_is_preprocessing = false; |
260 | m_nodes_dont_preprocess.clear(); |
261 | } |
262 | |
263 | |
264 | |
265 | void QSGRenderer::addNodesToPreprocess(QSGNode *node) |
266 | { |
267 | for (QSGNode *c = node->firstChild(); c; c = c->nextSibling()) |
268 | addNodesToPreprocess(node: c); |
269 | if (node->flags() & QSGNode::UsePreprocess) |
270 | m_nodes_to_preprocess.insert(value: node); |
271 | } |
272 | |
273 | void QSGRenderer::removeNodesToPreprocess(QSGNode *node) |
274 | { |
275 | for (QSGNode *c = node->firstChild(); c; c = c->nextSibling()) |
276 | removeNodesToPreprocess(node: c); |
277 | if (node->flags() & QSGNode::UsePreprocess) { |
278 | m_nodes_to_preprocess.remove(value: node); |
279 | |
280 | // If preprocessing *now*, mark the node as gone. |
281 | if (m_is_preprocessing) |
282 | m_nodes_dont_preprocess.insert(value: node); |
283 | } |
284 | } |
285 | |
286 | void QSGRenderer::prepareInline() |
287 | { |
288 | } |
289 | |
290 | void QSGRenderer::renderInline() |
291 | { |
292 | } |
293 | |
294 | |
295 | /*! |
296 | \class QSGNodeDumper |
297 | \brief The QSGNodeDumper class provides a way of dumping a scene grahp to the console. |
298 | |
299 | This class is solely for debugging purposes. |
300 | |
301 | \internal |
302 | */ |
303 | |
304 | void QSGNodeDumper::dump(QSGNode *n) |
305 | { |
306 | QSGNodeDumper dump; |
307 | dump.visitNode(n); |
308 | } |
309 | |
310 | void QSGNodeDumper::visitNode(QSGNode *n) |
311 | { |
312 | qDebug() << QByteArray(m_indent * 2, ' ').constData() << n; |
313 | QSGNodeVisitor::visitNode(n); |
314 | } |
315 | |
316 | void QSGNodeDumper::visitChildren(QSGNode *n) |
317 | { |
318 | ++m_indent; |
319 | QSGNodeVisitor::visitChildren(n); |
320 | --m_indent; |
321 | } |
322 | |
323 | |
324 | QT_END_NAMESPACE |
325 |
Definitions
Learn Advanced QML with KDAB
Find out more