Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qsgabstractsoftwarerenderer_p.h"
#include "qsgsoftwarerenderablenodeupdater_p.h"
#include "qsgsoftwarerenderlistbuilder_p.h"
#include "qsgsoftwarecontext_p.h"
#include "qsgsoftwarerenderablenode_p.h"
#include <QtCore/QLoggingCategory>
#include <QtGui/QWindow>
#include <QtQuick/QSGSimpleRectNode>
Q_STATIC_LOGGING_CATEGORY(lc2DRender, "qt.scenegraph.softwarecontext.abstractrenderer")
QT_BEGIN_NAMESPACE
QSGAbstractSoftwareRenderer::QSGAbstractSoftwareRenderer(QSGRenderContext *context)
: QSGRenderer(context)
, m_background(new QSGSimpleRectNode)
, m_nodeUpdater(new QSGSoftwareRenderableNodeUpdater(this))
{
// Setup special background node
auto backgroundRenderable = new QSGSoftwareRenderableNode(QSGSoftwareRenderableNode::SimpleRect, m_background);
addNodeMapping(m_background, backgroundRenderable);
}
QSGAbstractSoftwareRenderer::~QSGAbstractSoftwareRenderer()
{
// Cleanup RenderableNodes
delete m_background;
qDeleteAll(m_nodes);
delete m_nodeUpdater;
}
QSGSoftwareRenderableNode *QSGAbstractSoftwareRenderer::renderableNode(QSGNode *node) const
{
return m_nodes.value(node, nullptr);
}
// Used by GammaRay
const QList<QSGSoftwareRenderableNode*> &QSGAbstractSoftwareRenderer::renderableNodes() const
{
return m_renderableNodes;
}
void QSGAbstractSoftwareRenderer::addNodeMapping(QSGNode *node, QSGSoftwareRenderableNode *renderableNode)
{
m_nodes.insert(node, renderableNode);
}
void QSGAbstractSoftwareRenderer::appendRenderableNode(QSGSoftwareRenderableNode *node)
{
m_renderableNodes.append(node);
}
void QSGAbstractSoftwareRenderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
{
if (state & QSGNode::DirtyGeometry) {
nodeGeometryUpdated(node);
}
if (state & QSGNode::DirtyMaterial) {
nodeMaterialUpdated(node);
}
if (state & QSGNode::DirtyMatrix) {
nodeMatrixUpdated(node);
}
if (state & QSGNode::DirtyNodeAdded) {
nodeAdded(node);
}
if (state & QSGNode::DirtyNodeRemoved) {
nodeRemoved(node);
}
if (state & QSGNode::DirtyOpacity) {
nodeOpacityUpdated(node);
}
if (state & QSGNode::DirtySubtreeBlocked) {
m_nodeUpdater->updateNodes(node);
}
if (state & QSGNode::DirtyForceUpdate) {
m_nodeUpdater->updateNodes(node);
}
QSGRenderer::nodeChanged(node, state);
}
QRegion QSGAbstractSoftwareRenderer::renderNodes(QPainter *painter)
{
QRegion dirtyRegion;
// If there are no nodes, do nothing
if (m_renderableNodes.isEmpty())
return dirtyRegion;
auto iterator = m_renderableNodes.begin();
// First node is the background and needs to painted without blending
if (m_clearColorEnabled) {
auto backgroundNode = *iterator;
dirtyRegion += backgroundNode->renderNode(painter, /*force opaque painting*/ true);
}
iterator++;
for (; iterator != m_renderableNodes.end(); ++iterator) {
auto node = *iterator;
dirtyRegion += node->renderNode(painter);
}
return dirtyRegion;
}
void QSGAbstractSoftwareRenderer::buildRenderList()
{
// Clear the previous renderlist
m_renderableNodes.clear();
// Add the background renderable (always first)
m_renderableNodes.append(renderableNode(m_background));
// Build the renderlist
QSGSoftwareRenderListBuilder(this).visitChildren(rootNode());
}
QRegion QSGAbstractSoftwareRenderer::optimizeRenderList()
{
// Iterate through the renderlist from front to back
// Objective is to update the dirty status and rects.
for (auto i = m_renderableNodes.rbegin(); i != m_renderableNodes.rend(); ++i) {
auto node = *i;
// Track the original version of isDirty() as it can change if
// when we subtract dirty regions but still need to mark the previously
// dirty region as dirty.
const bool wasDirty = node->isDirty();
if (!m_dirtyRegion.isEmpty()) {
// See if the current dirty regions apply to the current node
node->addDirtyRegion(m_dirtyRegion, true);
}
if (!m_obscuredRegion.isEmpty()) {
// Don't try to paint things that are covered by opaque objects
node->subtractDirtyRegion(m_obscuredRegion);
}
// Keep up with obscured regions
if (node->isOpaque()) {
m_obscuredRegion += node->boundingRectMin();
}
if (node->isDirty()) {
// Don't paint things outside of the rendering area
if (!m_background->rect().toRect().contains(node->boundingRectMax(), /*proper*/ true)) {
// Some part(s) of node is(are) outside of the rendering area
QRegion renderArea(m_background->rect().toRect());
QRegion outsideRegions = node->dirtyRegion().subtracted(renderArea);
if (!outsideRegions.isEmpty())
node->subtractDirtyRegion(outsideRegions);
}
// Get the dirty region's to pass to the next nodes
if (node->isOpaque()) {
// if isOpaque, subtract node's dirty rect from m_dirtyRegion
// node->boundingRectMax() is the area that might have been changed
// node->boundingRectMin() is the area that will have been changed
// This adding and subtracting will make sure that the "maybe" area is
// also marked dirty for the next nodes to prevent artifacts
m_dirtyRegion += node->boundingRectMax();
m_dirtyRegion -= node->boundingRectMin();
} else {
// if isAlpha, add node's dirty rect to m_dirtyRegion
m_dirtyRegion += node->dirtyRegion();
}
}
if (wasDirty) {
// If this node started out dirty, make sure its previous region is
// added to the dirty region so that it gets cleared properly.
QRegion prevDirty = node->previousDirtyRegion();
if (!prevDirty.isNull())
m_dirtyRegion += prevDirty;
}
}
if (m_obscuredRegion.contains(m_background->rect().toAlignedRect())) {
m_isOpaque = true;
} else {
m_isOpaque = false;
}
// Empty dirtyRegion (for second pass)
m_dirtyRegion = QRegion();
m_obscuredRegion = QRegion();
// Iterate through the renderlist from back to front
// Objective is to make sure all non-opaque items are painted when an item under them is dirty
for (auto j = m_renderableNodes.begin(); j != m_renderableNodes.end(); ++j) {
auto node = *j;
if ((!node->isOpaque() || node->boundingRectMax() != node->boundingRectMin()) && !m_dirtyRegion.isEmpty()) {
// Blended nodes need to be updated
// QTBUG-113745: Also nodes with floating point boundary rectangles need to
// be updated. The reason is that m_obscuredRegion contains only the rounded
// down bounding rectangle (node->boundingRectMin()) and thus not the whole
// node. As a result up to 1 pixel would be overpainted when it should not.
node->addDirtyRegion(m_dirtyRegion, true);
}
m_dirtyRegion += node->dirtyRegion();
}
QRegion updateRegion = m_dirtyRegion;
// Empty dirtyRegion
m_dirtyRegion = QRegion();
m_obscuredRegion = QRegion();
return updateRegion;
}
void QSGAbstractSoftwareRenderer::setBackgroundColor(const QColor &color)
{
if (m_background->color() == color)
return;
m_background->setColor(color);
renderableNode(m_background)->markMaterialDirty();
}
void QSGAbstractSoftwareRenderer::setBackgroundRect(const QRect &rect, qreal devicePixelRatio)
{
if (m_background->rect().toRect() == rect && m_devicePixelRatio == devicePixelRatio)
return;
m_background->setRect(rect);
m_devicePixelRatio = devicePixelRatio;
renderableNode(m_background)->markGeometryDirty();
// Invalidate the whole scene when the background is resized
markDirty();
}
QColor QSGAbstractSoftwareRenderer::backgroundColor()
{
return m_background->color();
}
QRect QSGAbstractSoftwareRenderer::backgroundRect()
{
return m_background->rect().toRect();
}
void QSGAbstractSoftwareRenderer::nodeAdded(QSGNode *node)
{
qCDebug(lc2DRender, "nodeAdded %p", (void*)node);
m_nodeUpdater->updateNodes(node);
}
void QSGAbstractSoftwareRenderer::nodeRemoved(QSGNode *node)
{
qCDebug(lc2DRender, "nodeRemoved %p", (void*)node);
auto renderable = renderableNode(node);
// remove mapping
if (renderable != nullptr) {
// Need to mark this region dirty in the other nodes
QRegion dirtyRegion = renderable->previousDirtyRegion(true);
if (dirtyRegion.isEmpty())
dirtyRegion = renderable->boundingRectMax();
m_dirtyRegion += dirtyRegion;
m_nodes.remove(node);
delete renderable;
}
// Remove all children nodes as well
for (QSGNode *child = node->firstChild(); child; child = child->nextSibling()) {
nodeRemoved(child);
}
m_nodeUpdater->updateNodes(node, true);
}
void QSGAbstractSoftwareRenderer::nodeGeometryUpdated(QSGNode *node)
{
qCDebug(lc2DRender, "nodeGeometryUpdated");
// Mark node as dirty
auto renderable = renderableNode(node);
if (renderable != nullptr) {
renderable->markGeometryDirty();
} else {
m_nodeUpdater->updateNodes(node);
}
}
void QSGAbstractSoftwareRenderer::nodeMaterialUpdated(QSGNode *node)
{
qCDebug(lc2DRender, "nodeMaterialUpdated");
// Mark node as dirty
auto renderable = renderableNode(node);
if (renderable != nullptr) {
renderable->markMaterialDirty();
} else {
m_nodeUpdater->updateNodes(node);
}
}
void QSGAbstractSoftwareRenderer::nodeMatrixUpdated(QSGNode *node)
{
qCDebug(lc2DRender, "nodeMaterialUpdated");
// Update children nodes
m_nodeUpdater->updateNodes(node);
}
void QSGAbstractSoftwareRenderer::nodeOpacityUpdated(QSGNode *node)
{
qCDebug(lc2DRender, "nodeOpacityUpdated");
// Update children nodes
m_nodeUpdater->updateNodes(node);
}
void QSGAbstractSoftwareRenderer::markDirty()
{
m_dirtyRegion = QRegion(m_background->rect().toRect());
}
void QSGAbstractSoftwareRenderer::setClearColorEnabled(bool enable)
{
m_clearColorEnabled = enable;
}
bool QSGAbstractSoftwareRenderer::clearColorEnabled() const
{
return m_clearColorEnabled;
}
QT_END_NAMESPACE