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 "qsgabstractsoftwarerenderer_p.h"
5
6#include "qsgsoftwarerenderablenodeupdater_p.h"
7#include "qsgsoftwarerenderlistbuilder_p.h"
8#include "qsgsoftwarecontext_p.h"
9#include "qsgsoftwarerenderablenode_p.h"
10
11#include <QtCore/QLoggingCategory>
12#include <QtGui/QWindow>
13#include <QtQuick/QSGSimpleRectNode>
14
15Q_LOGGING_CATEGORY(lc2DRender, "qt.scenegraph.softwarecontext.abstractrenderer")
16
17QT_BEGIN_NAMESPACE
18
19QSGAbstractSoftwareRenderer::QSGAbstractSoftwareRenderer(QSGRenderContext *context)
20 : QSGRenderer(context)
21 , m_background(new QSGSimpleRectNode)
22 , m_nodeUpdater(new QSGSoftwareRenderableNodeUpdater(this))
23{
24 // Setup special background node
25 auto backgroundRenderable = new QSGSoftwareRenderableNode(QSGSoftwareRenderableNode::SimpleRect, m_background);
26 addNodeMapping(node: m_background, renderableNode: backgroundRenderable);
27}
28
29QSGAbstractSoftwareRenderer::~QSGAbstractSoftwareRenderer()
30{
31 // Cleanup RenderableNodes
32 delete m_background;
33
34 qDeleteAll(c: m_nodes);
35
36 delete m_nodeUpdater;
37}
38
39QSGSoftwareRenderableNode *QSGAbstractSoftwareRenderer::renderableNode(QSGNode *node) const
40{
41 return m_nodes.value(key: node, defaultValue: nullptr);
42}
43
44// Used by GammaRay
45const QVector<QSGSoftwareRenderableNode*> &QSGAbstractSoftwareRenderer::renderableNodes() const
46{
47 return m_renderableNodes;
48}
49
50void QSGAbstractSoftwareRenderer::addNodeMapping(QSGNode *node, QSGSoftwareRenderableNode *renderableNode)
51{
52 m_nodes.insert(key: node, value: renderableNode);
53}
54
55void QSGAbstractSoftwareRenderer::appendRenderableNode(QSGSoftwareRenderableNode *node)
56{
57 m_renderableNodes.append(t: node);
58}
59
60void QSGAbstractSoftwareRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
61{
62 if (state & QSGNode::DirtyGeometry) {
63 nodeGeometryUpdated(node);
64 }
65 if (state & QSGNode::DirtyMaterial) {
66 nodeMaterialUpdated(node);
67 }
68 if (state & QSGNode::DirtyMatrix) {
69 nodeMatrixUpdated(node);
70 }
71 if (state & QSGNode::DirtyNodeAdded) {
72 nodeAdded(node);
73 }
74 if (state & QSGNode::DirtyNodeRemoved) {
75 nodeRemoved(node);
76 }
77 if (state & QSGNode::DirtyOpacity) {
78 nodeOpacityUpdated(node);
79 }
80 if (state & QSGNode::DirtySubtreeBlocked) {
81 m_nodeUpdater->updateNodes(node);
82 }
83 if (state & QSGNode::DirtyForceUpdate) {
84 m_nodeUpdater->updateNodes(node);
85 }
86 QSGRenderer::nodeChanged(node, state);
87}
88
89QRegion QSGAbstractSoftwareRenderer::renderNodes(QPainter *painter)
90{
91 QRegion dirtyRegion;
92 // If there are no nodes, do nothing
93 if (m_renderableNodes.isEmpty())
94 return dirtyRegion;
95
96 auto iterator = m_renderableNodes.begin();
97 // First node is the background and needs to painted without blending
98 auto backgroundNode = *iterator;
99 dirtyRegion += backgroundNode->renderNode(painter, /*force opaque painting*/ forceOpaquePainting: true);
100 iterator++;
101
102 for (; iterator != m_renderableNodes.end(); ++iterator) {
103 auto node = *iterator;
104 dirtyRegion += node->renderNode(painter);
105 }
106
107 return dirtyRegion;
108}
109
110void QSGAbstractSoftwareRenderer::buildRenderList()
111{
112 // Clear the previous renderlist
113 m_renderableNodes.clear();
114 // Add the background renderable (always first)
115 m_renderableNodes.append(t: renderableNode(node: m_background));
116 // Build the renderlist
117 QSGSoftwareRenderListBuilder(this).visitChildren(node: rootNode());
118}
119
120QRegion QSGAbstractSoftwareRenderer::optimizeRenderList()
121{
122 // Iterate through the renderlist from front to back
123 // Objective is to update the dirty status and rects.
124 for (auto i = m_renderableNodes.rbegin(); i != m_renderableNodes.rend(); ++i) {
125 auto node = *i;
126 // Track the original version of isDirty() as it can change if
127 // when we subtract dirty regions but still need to mark the previously
128 // dirty region as dirty.
129 const bool wasDirty = node->isDirty();
130 if (!m_dirtyRegion.isEmpty()) {
131 // See if the current dirty regions apply to the current node
132 node->addDirtyRegion(dirtyRegion: m_dirtyRegion, forceDirty: true);
133 }
134
135 if (!m_obscuredRegion.isEmpty()) {
136 // Don't try to paint things that are covered by opaque objects
137 node->subtractDirtyRegion(dirtyRegion: m_obscuredRegion);
138 }
139
140 // Keep up with obscured regions
141 if (node->isOpaque()) {
142 m_obscuredRegion += node->boundingRectMin();
143 }
144
145 if (node->isDirty()) {
146 // Don't paint things outside of the rendering area
147 if (!m_background->rect().toRect().contains(r: node->boundingRectMax(), /*proper*/ true)) {
148 // Some part(s) of node is(are) outside of the rendering area
149 QRegion renderArea(m_background->rect().toRect());
150 QRegion outsideRegions = node->dirtyRegion().subtracted(r: renderArea);
151 if (!outsideRegions.isEmpty())
152 node->subtractDirtyRegion(dirtyRegion: outsideRegions);
153 }
154
155 // Get the dirty region's to pass to the next nodes
156 if (node->isOpaque()) {
157 // if isOpaque, subtract node's dirty rect from m_dirtyRegion
158 m_dirtyRegion -= node->boundingRectMin();
159 } else {
160 // if isAlpha, add node's dirty rect to m_dirtyRegion
161 m_dirtyRegion += node->dirtyRegion();
162 }
163 }
164
165 if (wasDirty) {
166 // If this node started out dirty, make sure its previous region is
167 // added to the dirty region so that it gets cleared properly.
168 QRegion prevDirty = node->previousDirtyRegion();
169 if (!prevDirty.isNull())
170 m_dirtyRegion += prevDirty;
171 }
172 }
173
174 if (m_obscuredRegion.contains(r: m_background->rect().toAlignedRect())) {
175 m_isOpaque = true;
176 } else {
177 m_isOpaque = false;
178 }
179
180 // Empty dirtyRegion (for second pass)
181 m_dirtyRegion = QRegion();
182 m_obscuredRegion = QRegion();
183
184 // Iterate through the renderlist from back to front
185 // Objective is to make sure all non-opaque items are painted when an item under them is dirty
186 for (auto j = m_renderableNodes.begin(); j != m_renderableNodes.end(); ++j) {
187 auto node = *j;
188
189 if ((!node->isOpaque() || node->boundingRectMax() != node->boundingRectMin()) && !m_dirtyRegion.isEmpty()) {
190 // Blended nodes need to be updated
191 // QTBUG-113745: Also nodes with floating point boundary rectangles need to
192 // be updated. The reason is that m_obscuredRegion contains only the rounded
193 // down bounding rectangle (node->boundingRectMin()) and thus not the whole
194 // node. As a result up to 1 pixel would be overpainted when it should not.
195 node->addDirtyRegion(dirtyRegion: m_dirtyRegion, forceDirty: true);
196 }
197
198 m_dirtyRegion += node->dirtyRegion();
199 }
200
201 QRegion updateRegion = m_dirtyRegion;
202
203 // Empty dirtyRegion
204 m_dirtyRegion = QRegion();
205 m_obscuredRegion = QRegion();
206
207 return updateRegion;
208}
209
210void QSGAbstractSoftwareRenderer::setBackgroundColor(const QColor &color)
211{
212 if (m_background->color() == color)
213 return;
214 m_background->setColor(color);
215 renderableNode(node: m_background)->markMaterialDirty();
216}
217
218void QSGAbstractSoftwareRenderer::setBackgroundRect(const QRect &rect, qreal devicePixelRatio)
219{
220 if (m_background->rect().toRect() == rect && m_devicePixelRatio == devicePixelRatio)
221 return;
222 m_background->setRect(rect);
223 m_devicePixelRatio = devicePixelRatio;
224 renderableNode(node: m_background)->markGeometryDirty();
225 // Invalidate the whole scene when the background is resized
226 markDirty();
227}
228
229QColor QSGAbstractSoftwareRenderer::backgroundColor()
230{
231 return m_background->color();
232}
233
234QRect QSGAbstractSoftwareRenderer::backgroundRect()
235{
236 return m_background->rect().toRect();
237}
238
239void QSGAbstractSoftwareRenderer::nodeAdded(QSGNode *node)
240{
241 qCDebug(lc2DRender, "nodeAdded %p", (void*)node);
242
243 m_nodeUpdater->updateNodes(node);
244}
245
246void QSGAbstractSoftwareRenderer::nodeRemoved(QSGNode *node)
247{
248 qCDebug(lc2DRender, "nodeRemoved %p", (void*)node);
249
250 auto renderable = renderableNode(node);
251 // remove mapping
252 if (renderable != nullptr) {
253 // Need to mark this region dirty in the other nodes
254 QRegion dirtyRegion = renderable->previousDirtyRegion(wasRemoved: true);
255 if (dirtyRegion.isEmpty())
256 dirtyRegion = renderable->boundingRectMax();
257 m_dirtyRegion += dirtyRegion;
258 m_nodes.remove(key: node);
259 delete renderable;
260 }
261
262 // Remove all children nodes as well
263 for (QSGNode *child = node->firstChild(); child; child = child->nextSibling()) {
264 nodeRemoved(node: child);
265 }
266
267 m_nodeUpdater->updateNodes(node, isNodeRemoved: true);
268}
269
270void QSGAbstractSoftwareRenderer::nodeGeometryUpdated(QSGNode *node)
271{
272 qCDebug(lc2DRender, "nodeGeometryUpdated");
273
274 // Mark node as dirty
275 auto renderable = renderableNode(node);
276 if (renderable != nullptr) {
277 renderable->markGeometryDirty();
278 } else {
279 m_nodeUpdater->updateNodes(node);
280 }
281}
282
283void QSGAbstractSoftwareRenderer::nodeMaterialUpdated(QSGNode *node)
284{
285 qCDebug(lc2DRender, "nodeMaterialUpdated");
286
287 // Mark node as dirty
288 auto renderable = renderableNode(node);
289 if (renderable != nullptr) {
290 renderable->markMaterialDirty();
291 } else {
292 m_nodeUpdater->updateNodes(node);
293 }
294}
295
296void QSGAbstractSoftwareRenderer::nodeMatrixUpdated(QSGNode *node)
297{
298 qCDebug(lc2DRender, "nodeMaterialUpdated");
299
300 // Update children nodes
301 m_nodeUpdater->updateNodes(node);
302}
303
304void QSGAbstractSoftwareRenderer::nodeOpacityUpdated(QSGNode *node)
305{
306 qCDebug(lc2DRender, "nodeOpacityUpdated");
307
308 // Update children nodes
309 m_nodeUpdater->updateNodes(node);
310}
311
312void QSGAbstractSoftwareRenderer::markDirty()
313{
314 m_dirtyRegion = QRegion(m_background->rect().toRect());
315}
316
317QT_END_NAMESPACE
318

source code of qtdeclarative/src/quick/scenegraph/adaptations/software/qsgabstractsoftwarerenderer.cpp