1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qsgabstractsoftwarerenderer_p.h" |
41 | |
42 | #include "qsgsoftwarerenderablenodeupdater_p.h" |
43 | #include "qsgsoftwarerenderlistbuilder_p.h" |
44 | #include "qsgsoftwarecontext_p.h" |
45 | #include "qsgsoftwarerenderablenode_p.h" |
46 | |
47 | #include <QtCore/QLoggingCategory> |
48 | #include <QtGui/QWindow> |
49 | #include <QtQuick/QSGSimpleRectNode> |
50 | |
51 | Q_LOGGING_CATEGORY(lc2DRender, "qt.scenegraph.softwarecontext.abstractrenderer" ) |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | QSGAbstractSoftwareRenderer::QSGAbstractSoftwareRenderer(QSGRenderContext *context) |
56 | : QSGRenderer(context) |
57 | , m_background(new QSGSimpleRectNode) |
58 | , m_nodeUpdater(new QSGSoftwareRenderableNodeUpdater(this)) |
59 | { |
60 | // Setup special background node |
61 | auto backgroundRenderable = new QSGSoftwareRenderableNode(QSGSoftwareRenderableNode::SimpleRect, m_background); |
62 | addNodeMapping(node: m_background, renderableNode: backgroundRenderable); |
63 | } |
64 | |
65 | QSGAbstractSoftwareRenderer::~QSGAbstractSoftwareRenderer() |
66 | { |
67 | // Cleanup RenderableNodes |
68 | delete m_background; |
69 | |
70 | qDeleteAll(c: m_nodes); |
71 | |
72 | delete m_nodeUpdater; |
73 | } |
74 | |
75 | QSGSoftwareRenderableNode *QSGAbstractSoftwareRenderer::renderableNode(QSGNode *node) const |
76 | { |
77 | return m_nodes.value(akey: node, adefaultValue: nullptr); |
78 | } |
79 | |
80 | void QSGAbstractSoftwareRenderer::addNodeMapping(QSGNode *node, QSGSoftwareRenderableNode *renderableNode) |
81 | { |
82 | m_nodes.insert(akey: node, avalue: renderableNode); |
83 | } |
84 | |
85 | void QSGAbstractSoftwareRenderer::appendRenderableNode(QSGSoftwareRenderableNode *node) |
86 | { |
87 | m_renderableNodes.append(t: node); |
88 | } |
89 | |
90 | void QSGAbstractSoftwareRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state) |
91 | { |
92 | if (state & QSGNode::DirtyGeometry) { |
93 | nodeGeometryUpdated(node); |
94 | } |
95 | if (state & QSGNode::DirtyMaterial) { |
96 | nodeMaterialUpdated(node); |
97 | } |
98 | if (state & QSGNode::DirtyMatrix) { |
99 | nodeMatrixUpdated(node); |
100 | } |
101 | if (state & QSGNode::DirtyNodeAdded) { |
102 | nodeAdded(node); |
103 | } |
104 | if (state & QSGNode::DirtyNodeRemoved) { |
105 | nodeRemoved(node); |
106 | } |
107 | if (state & QSGNode::DirtyOpacity) { |
108 | nodeOpacityUpdated(node); |
109 | } |
110 | if (state & QSGNode::DirtySubtreeBlocked) { |
111 | m_nodeUpdater->updateNodes(node); |
112 | } |
113 | if (state & QSGNode::DirtyForceUpdate) { |
114 | m_nodeUpdater->updateNodes(node); |
115 | } |
116 | QSGRenderer::nodeChanged(node, state); |
117 | } |
118 | |
119 | QRegion QSGAbstractSoftwareRenderer::renderNodes(QPainter *painter) |
120 | { |
121 | QRegion dirtyRegion; |
122 | // If there are no nodes, do nothing |
123 | if (m_renderableNodes.isEmpty()) |
124 | return dirtyRegion; |
125 | |
126 | auto iterator = m_renderableNodes.begin(); |
127 | // First node is the background and needs to painted without blending |
128 | auto backgroundNode = *iterator; |
129 | dirtyRegion += backgroundNode->renderNode(painter, /*force opaque painting*/ forceOpaquePainting: true); |
130 | iterator++; |
131 | |
132 | for (; iterator != m_renderableNodes.end(); ++iterator) { |
133 | auto node = *iterator; |
134 | dirtyRegion += node->renderNode(painter); |
135 | } |
136 | |
137 | return dirtyRegion; |
138 | } |
139 | |
140 | void QSGAbstractSoftwareRenderer::buildRenderList() |
141 | { |
142 | // Clear the previous renderlist |
143 | m_renderableNodes.clear(); |
144 | // Add the background renderable (always first) |
145 | m_renderableNodes.append(t: renderableNode(node: m_background)); |
146 | // Build the renderlist |
147 | QSGSoftwareRenderListBuilder(this).visitChildren(node: rootNode()); |
148 | } |
149 | |
150 | QRegion QSGAbstractSoftwareRenderer::optimizeRenderList() |
151 | { |
152 | // Iterate through the renderlist from front to back |
153 | // Objective is to update the dirty status and rects. |
154 | for (auto i = m_renderableNodes.rbegin(); i != m_renderableNodes.rend(); ++i) { |
155 | auto node = *i; |
156 | if (!m_dirtyRegion.isEmpty()) { |
157 | // See if the current dirty regions apply to the current node |
158 | node->addDirtyRegion(dirtyRegion: m_dirtyRegion, forceDirty: true); |
159 | } |
160 | |
161 | if (!m_obscuredRegion.isEmpty()) { |
162 | // Don't try to paint things that are covered by opaque objects |
163 | node->subtractDirtyRegion(dirtyRegion: m_obscuredRegion); |
164 | } |
165 | |
166 | // Keep up with obscured regions |
167 | if (node->isOpaque()) { |
168 | m_obscuredRegion += node->boundingRectMin(); |
169 | } |
170 | |
171 | if (node->isDirty()) { |
172 | // Don't paint things outside of the rendering area |
173 | if (!m_background->rect().toRect().contains(r: node->boundingRectMax(), /*proper*/ true)) { |
174 | // Some part(s) of node is(are) outside of the rendering area |
175 | QRegion renderArea(m_background->rect().toRect()); |
176 | QRegion outsideRegions = node->dirtyRegion().subtracted(r: renderArea); |
177 | if (!outsideRegions.isEmpty()) |
178 | node->subtractDirtyRegion(dirtyRegion: outsideRegions); |
179 | } |
180 | |
181 | // Get the dirty region's to pass to the next nodes |
182 | if (node->isOpaque()) { |
183 | // if isOpaque, subtract node's dirty rect from m_dirtyRegion |
184 | m_dirtyRegion -= node->boundingRectMin(); |
185 | } else { |
186 | // if isAlpha, add node's dirty rect to m_dirtyRegion |
187 | m_dirtyRegion += node->dirtyRegion(); |
188 | } |
189 | // if previousDirtyRegion has content outside of boundingRect add to m_dirtyRegion |
190 | QRegion prevDirty = node->previousDirtyRegion(); |
191 | if (!prevDirty.isNull()) |
192 | m_dirtyRegion += prevDirty; |
193 | } |
194 | } |
195 | |
196 | if (m_obscuredRegion.contains(r: m_background->rect().toAlignedRect())) { |
197 | m_isOpaque = true; |
198 | } else { |
199 | m_isOpaque = false; |
200 | } |
201 | |
202 | // Empty dirtyRegion (for second pass) |
203 | m_dirtyRegion = QRegion(); |
204 | m_obscuredRegion = QRegion(); |
205 | |
206 | // Iterate through the renderlist from back to front |
207 | // Objective is to make sure all non-opaque items are painted when an item under them is dirty |
208 | for (auto j = m_renderableNodes.begin(); j != m_renderableNodes.end(); ++j) { |
209 | auto node = *j; |
210 | |
211 | if (!node->isOpaque() && !m_dirtyRegion.isEmpty()) { |
212 | // Only blended nodes need to be updated |
213 | node->addDirtyRegion(dirtyRegion: m_dirtyRegion, forceDirty: true); |
214 | } |
215 | |
216 | m_dirtyRegion += node->dirtyRegion(); |
217 | } |
218 | |
219 | QRegion updateRegion = m_dirtyRegion; |
220 | |
221 | // Empty dirtyRegion |
222 | m_dirtyRegion = QRegion(); |
223 | m_obscuredRegion = QRegion(); |
224 | |
225 | return updateRegion; |
226 | } |
227 | |
228 | void QSGAbstractSoftwareRenderer::setBackgroundColor(const QColor &color) |
229 | { |
230 | if (m_background->color() == color) |
231 | return; |
232 | m_background->setColor(color); |
233 | renderableNode(node: m_background)->markMaterialDirty(); |
234 | } |
235 | |
236 | void QSGAbstractSoftwareRenderer::setBackgroundRect(const QRect &rect, qreal devicePixelRatio) |
237 | { |
238 | if (m_background->rect().toRect() == rect && m_devicePixelRatio == devicePixelRatio) |
239 | return; |
240 | m_background->setRect(rect); |
241 | m_devicePixelRatio = devicePixelRatio; |
242 | renderableNode(node: m_background)->markGeometryDirty(); |
243 | // Invalidate the whole scene when the background is resized |
244 | markDirty(); |
245 | } |
246 | |
247 | QColor QSGAbstractSoftwareRenderer::backgroundColor() |
248 | { |
249 | return m_background->color(); |
250 | } |
251 | |
252 | QRect QSGAbstractSoftwareRenderer::backgroundRect() |
253 | { |
254 | return m_background->rect().toRect(); |
255 | } |
256 | |
257 | void QSGAbstractSoftwareRenderer::nodeAdded(QSGNode *node) |
258 | { |
259 | qCDebug(lc2DRender, "nodeAdded %p" , (void*)node); |
260 | |
261 | m_nodeUpdater->updateNodes(node); |
262 | } |
263 | |
264 | void QSGAbstractSoftwareRenderer::nodeRemoved(QSGNode *node) |
265 | { |
266 | qCDebug(lc2DRender, "nodeRemoved %p" , (void*)node); |
267 | |
268 | auto renderable = renderableNode(node); |
269 | // remove mapping |
270 | if (renderable != nullptr) { |
271 | // Need to mark this region dirty in the other nodes |
272 | QRegion dirtyRegion = renderable->previousDirtyRegion(wasRemoved: true); |
273 | if (dirtyRegion.isEmpty()) |
274 | dirtyRegion = renderable->boundingRectMax(); |
275 | m_dirtyRegion += dirtyRegion; |
276 | m_nodes.remove(akey: node); |
277 | delete renderable; |
278 | } |
279 | |
280 | // Remove all children nodes as well |
281 | for (QSGNode *child = node->firstChild(); child; child = child->nextSibling()) { |
282 | nodeRemoved(node: child); |
283 | } |
284 | |
285 | m_nodeUpdater->updateNodes(node, isNodeRemoved: true); |
286 | } |
287 | |
288 | void QSGAbstractSoftwareRenderer::nodeGeometryUpdated(QSGNode *node) |
289 | { |
290 | qCDebug(lc2DRender, "nodeGeometryUpdated" ); |
291 | |
292 | // Mark node as dirty |
293 | auto renderable = renderableNode(node); |
294 | if (renderable != nullptr) { |
295 | renderable->markGeometryDirty(); |
296 | } else { |
297 | m_nodeUpdater->updateNodes(node); |
298 | } |
299 | } |
300 | |
301 | void QSGAbstractSoftwareRenderer::nodeMaterialUpdated(QSGNode *node) |
302 | { |
303 | qCDebug(lc2DRender, "nodeMaterialUpdated" ); |
304 | |
305 | // Mark node as dirty |
306 | auto renderable = renderableNode(node); |
307 | if (renderable != nullptr) { |
308 | renderable->markMaterialDirty(); |
309 | } else { |
310 | m_nodeUpdater->updateNodes(node); |
311 | } |
312 | } |
313 | |
314 | void QSGAbstractSoftwareRenderer::nodeMatrixUpdated(QSGNode *node) |
315 | { |
316 | qCDebug(lc2DRender, "nodeMaterialUpdated" ); |
317 | |
318 | // Update children nodes |
319 | m_nodeUpdater->updateNodes(node); |
320 | } |
321 | |
322 | void QSGAbstractSoftwareRenderer::nodeOpacityUpdated(QSGNode *node) |
323 | { |
324 | qCDebug(lc2DRender, "nodeOpacityUpdated" ); |
325 | |
326 | // Update children nodes |
327 | m_nodeUpdater->updateNodes(node); |
328 | } |
329 | |
330 | void QSGAbstractSoftwareRenderer::markDirty() |
331 | { |
332 | m_dirtyRegion = QRegion(m_background->rect().toRect()); |
333 | } |
334 | |
335 | QT_END_NAMESPACE |
336 | |