/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsgsoftwarerenderablenode_p.h" #include "qsgsoftwareinternalimagenode_p.h" #include "qsgsoftwareinternalrectanglenode_p.h" #include "qsgsoftwareglyphnode_p.h" #include "qsgsoftwarepublicnodes_p.h" #include "qsgsoftwarepainternode_p.h" #include "qsgsoftwarepixmaptexture_p.h" #if QT_CONFIG(quick_sprite) #include "qsgsoftwarespritenode_p.h" #endif #include #include #include #include #include Q_LOGGING_CATEGORY(lcRenderable, "qt.scenegraph.softwarecontext.renderable") QT_BEGIN_NAMESPACE // Largest subrectangle with integer coordinates inline QRect toRectMin(const QRectF & r) { int x1 = qCeil(r.left()); int x2 = qFloor(r.right()); int y1 = qCeil(r.top()); int y2 = qFloor(r.bottom()); return QRect(x1, y1, x2 - x1, y2 - y1); } // Smallest superrectangle with integer coordinates inline QRect toRectMax(const QRectF & r) { return r.toAlignedRect(); } QSGSoftwareRenderableNode::QSGSoftwareRenderableNode(NodeType type, QSGNode *node) : m_nodeType(type) , m_isOpaque(true) , m_isDirty(true) , m_hasClipRegion(false) , m_opacity(1.0f) { switch (m_nodeType) { case QSGSoftwareRenderableNode::SimpleRect: m_handle.simpleRectNode = static_cast(node); break; case QSGSoftwareRenderableNode::SimpleTexture: m_handle.simpleTextureNode = static_cast(node); break; case QSGSoftwareRenderableNode::Image: m_handle.imageNode = static_cast(node); break; case QSGSoftwareRenderableNode::Painter: m_handle.painterNode = static_cast(node); break; case QSGSoftwareRenderableNode::Rectangle: m_handle.rectangleNode = static_cast(node); break; case QSGSoftwareRenderableNode::Glyph: m_handle.glpyhNode = static_cast(node); break; case QSGSoftwareRenderableNode::NinePatch: m_handle.ninePatchNode = static_cast(node); break; case QSGSoftwareRenderableNode::SimpleRectangle: m_handle.simpleRectangleNode = static_cast(node); break; case QSGSoftwareRenderableNode::SimpleImage: m_handle.simpleImageNode = static_cast(node); break; #if QT_CONFIG(quick_sprite) case QSGSoftwareRenderableNode::SpriteNode: m_handle.spriteNode = static_cast(node); break; #endif case QSGSoftwareRenderableNode::RenderNode: m_handle.renderNode = static_cast(node); break; case QSGSoftwareRenderableNode::Invalid: m_handle.simpleRectNode = nullptr; break; } } QSGSoftwareRenderableNode::~QSGSoftwareRenderableNode() { } void QSGSoftwareRenderableNode::update() { // Update the Node properties m_isDirty = true; m_isOpaque = false; QRectF boundingRect; switch (m_nodeType) { case QSGSoftwareRenderableNode::SimpleRect: if (m_handle.simpleRectNode->color().alpha() == 255) m_isOpaque = true; boundingRect = m_handle.simpleRectNode->rect(); break; case QSGSoftwareRenderableNode::SimpleTexture: if (!m_handle.simpleTextureNode->texture()->hasAlphaChannel()) m_isOpaque = true; boundingRect = m_handle.simpleTextureNode->rect(); break; case QSGSoftwareRenderableNode::Image: m_isOpaque = !m_handle.imageNode->pixmap().hasAlphaChannel(); boundingRect = m_handle.imageNode->rect().toRect(); break; case QSGSoftwareRenderableNode::Painter: if (m_handle.painterNode->opaquePainting()) m_isOpaque = true; boundingRect = QRectF(0, 0, m_handle.painterNode->size().width(), m_handle.painterNode->size().height()); break; case QSGSoftwareRenderableNode::Rectangle: if (m_handle.rectangleNode->isOpaque()) m_isOpaque = true; boundingRect = m_handle.rectangleNode->rect(); break; case QSGSoftwareRenderableNode::Glyph: // Always has alpha boundingRect = m_handle.glpyhNode->boundingRect(); break; case QSGSoftwareRenderableNode::NinePatch: m_isOpaque = m_handle.ninePatchNode->isOpaque(); boundingRect = m_handle.ninePatchNode->bounds(); break; case QSGSoftwareRenderableNode::SimpleRectangle: if (m_handle.simpleRectangleNode->color().alpha() == 255) m_isOpaque = true; boundingRect = m_handle.simpleRectangleNode->rect(); break; case QSGSoftwareRenderableNode::SimpleImage: if (!m_handle.simpleImageNode->texture()->hasAlphaChannel()) m_isOpaque = true; boundingRect = m_handle.simpleImageNode->rect(); break; #if QT_CONFIG(quick_sprite) case QSGSoftwareRenderableNode::SpriteNode: m_isOpaque = m_handle.spriteNode->isOpaque(); boundingRect = m_handle.spriteNode->rect(); break; #endif case QSGSoftwareRenderableNode::RenderNode: if (m_handle.renderNode->flags().testFlag(QSGRenderNode::OpaqueRendering)) m_isOpaque = true; boundingRect = m_handle.renderNode->rect(); break; default: break; } if (m_transform.isRotating()) m_isOpaque = false; const QRectF transformedRect = m_transform.mapRect(boundingRect); m_boundingRectMin = toRectMin(transformedRect); m_boundingRectMax = toRectMax(transformedRect); if (m_hasClipRegion && m_clipRegion.rectCount() <= 1) { // If there is a clipRegion, and it is empty, the item wont be rendered if (m_clipRegion.isEmpty()) { m_boundingRectMin = QRect(); m_boundingRectMax = QRect(); } else { m_boundingRectMin = m_boundingRectMin.intersected(m_clipRegion.rects().constFirst()); m_boundingRectMax = m_boundingRectMax.intersected(m_clipRegion.rects().constFirst()); } } // Overrides if (m_opacity < 1.0f) m_isOpaque = false; m_dirtyRegion = QRegion(m_boundingRectMax); } struct RenderNodeState : public QSGRenderNode::RenderState { const QMatrix4x4 *projectionMatrix() const override { return &ident; } QRect scissorRect() const override { return QRect(); } bool scissorEnabled() const override { return false; } int stencilValue() const override { return 0; } bool stencilEnabled() const override { return false; } const QRegion *clipRegion() const override { return &cr; } QMatrix4x4 ident; QRegion cr; }; QRegion QSGSoftwareRenderableNode::renderNode(QPainter *painter, bool forceOpaquePainting) { Q_ASSERT(painter); // Check for don't paint conditions if (m_nodeType != RenderNode) { if (!m_isDirty || qFuzzyIsNull(m_opacity) || m_dirtyRegion.isEmpty()) { m_isDirty = false; m_dirtyRegion = QRegion(); return QRegion(); } } else { if (!m_isDirty || qFuzzyIsNull(m_opacity)) { m_isDirty = false; m_dirtyRegion = QRegion(); return QRegion(); } else { QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(m_handle.renderNode); QMatrix4x4 m = m_transform; rd->m_matrix = &m; rd->m_opacity = m_opacity; // all the clip region below is in world coordinates, taking m_transform into account already QRegion cr = m_dirtyRegion; if (m_clipRegion.rectCount() > 1) cr &= m_clipRegion; painter->save(); RenderNodeState rs; rs.cr = cr; m_handle.renderNode->render(&rs); painter->restore(); const QRect br = m_handle.renderNode->flags().testFlag(QSGRenderNode::BoundedRectRendering) ? m_boundingRectMax // already mapped to world : QRect(0, 0, painter->device()->width(), painter->device()->height()); m_previousDirtyRegion = QRegion(br); m_isDirty = false; m_dirtyRegion = QRegion(); return br; } } painter->save(); painter->setOpacity(m_opacity); // Set clipRegion to m_dirtyRegion (in world coordinates, so must be done before the setTransform below) // as m_dirtyRegion already accounts for clipRegion painter->setClipRegion(m_dirtyRegion, Qt::ReplaceClip); if (m_clipRegion.rectCount() > 1) painter->setClipRegion(m_clipRegion, Qt::IntersectClip); painter->setTransform(m_transform, false); //precalculated worldTransform if (forceOpaquePainting || m_isOpaque) painter->setCompositionMode(QPainter::CompositionMode_Source); switch (m_nodeType) { case QSGSoftwareRenderableNode::SimpleRect: painter->fillRect(m_handle.simpleRectNode->rect(), m_handle.simpleRectNode->color()); break; case QSGSoftwareRenderableNode::SimpleTexture: { QSGTexture *texture = m_handle.simpleTextureNode->texture(); if (QSGSoftwarePixmapTexture *pt = qobject_cast(texture)) { const QPixmap &pm = pt->pixmap(); painter->drawPixmap(m_handle.simpleTextureNode->rect(), pm, m_handle.simpleTextureNode->sourceRect()); } else if (QSGPlainTexture *pt = qobject_cast(texture)) { const QImage &im = pt->image(); painter->drawImage(m_handle.simpleTextureNode->rect(), im, m_handle.simpleTextureNode->sourceRect()); } } break; case QSGSoftwareRenderableNode::Image: m_handle.imageNode->paint(painter); break; case QSGSoftwareRenderableNode::Painter: m_handle.painterNode->paint(painter); break; case QSGSoftwareRenderableNode::Rectangle: m_handle.rectangleNode->paint(painter); break; case QSGSoftwareRenderableNode::Glyph: m_handle.glpyhNode->paint(painter); break; case QSGSoftwareRenderableNode::NinePatch: m_handle.ninePatchNode->paint(painter); break; case QSGSoftwareRenderableNode::SimpleRectangle: static_cast(m_handle.simpleRectangleNode)->paint(painter); break; case QSGSoftwareRenderableNode::SimpleImage: static_cast(m_handle.simpleImageNode)->paint(painter); break; #if QT_CONFIG(quick_sprite) case QSGSoftwareRenderableNode::SpriteNode: static_cast(m_handle.spriteNode)->paint(painter); break; #endif default: break; } painter->restore(); QRegion areaToBeFlushed = m_dirtyRegion; m_previousDirtyRegion = QRegion(m_boundingRectMax); m_isDirty = false; m_dirtyRegion = QRegion(); return areaToBeFlushed; } bool QSGSoftwareRenderableNode::isDirtyRegionEmpty() const { return m_dirtyRegion.isEmpty(); } void QSGSoftwareRenderableNode::setTransform(const QTransform &transform) { if (m_transform == transform) return; m_transform = transform; update(); } void QSGSoftwareRenderableNode::setClipRegion(const QRegion &clipRect, bool hasClipRegion) { if (m_clipRegion == clipRect && m_hasClipRegion == hasClipRegion) return; m_clipRegion = clipRect; m_hasClipRegion = hasClipRegion; update(); } void QSGSoftwareRenderableNode::setOpacity(float opacity) { if (qFuzzyCompare(m_opacity, opacity)) return; m_opacity = opacity; update(); } void QSGSoftwareRenderableNode::markGeometryDirty() { update(); } void QSGSoftwareRenderableNode::markMaterialDirty() { update(); } void QSGSoftwareRenderableNode::addDirtyRegion(const QRegion &dirtyRegion, bool forceDirty) { // Check if the dirty region applies to this node QRegion prev = m_dirtyRegion; if (dirtyRegion.intersects(m_boundingRectMax)) { if (forceDirty) m_isDirty = true; m_dirtyRegion += dirtyRegion.intersected(m_boundingRectMax); } qCDebug(lcRenderable) << "addDirtyRegion: " << dirtyRegion << "old dirtyRegion: " << prev << "new dirtyRegion: " << m_dirtyRegion; } void QSGSoftwareRenderableNode::subtractDirtyRegion(const QRegion &dirtyRegion) { QRegion prev = m_dirtyRegion; if (m_isDirty) { // Check if this rect concerns us if (dirtyRegion.intersects(m_boundingRectMax)) { m_dirtyRegion -= dirtyRegion; if (m_dirtyRegion.isEmpty()) m_isDirty = false; } } qCDebug(lcRenderable) << "subtractDirtyRegion: " << dirtyRegion << "old dirtyRegion" << prev << "new dirtyRegion: " << m_dirtyRegion; } QRegion QSGSoftwareRenderableNode::previousDirtyRegion(bool wasRemoved) const { // When removing a node, the boundingRect shouldn't be subtracted // because a deleted node has no valid boundingRect if (wasRemoved) return m_previousDirtyRegion; return m_previousDirtyRegion.subtracted(QRegion(m_boundingRectMax)); } QRegion QSGSoftwareRenderableNode::dirtyRegion() const { return m_dirtyRegion; } QT_END_NAMESPACE