From 591fc55479a98497ebb405926dc7e58c0f9722a8 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 23 Sep 2024 15:38:12 +0200 Subject: [PATCH 1/3] clearifying comments --- src/nodes/QskFillNode.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nodes/QskFillNode.h b/src/nodes/QskFillNode.h index 2630bb0b..3bd8664b 100644 --- a/src/nodes/QskFillNode.h +++ b/src/nodes/QskFillNode.h @@ -36,15 +36,15 @@ class QSK_EXPORT QskFillNode : public QSGGeometryNode Colors might be defined in the material ( QskGradientMaterial, QSGFlatColorMaterial ) or attached to each point ( QSGVertexColorMaterial ). - The main advantage of using colored points is, that the material becomes - independent of the coloring and the scene graph is able to batch the nodes + Having colored points makes the material independent of the coloring + and the scene graph is able to batch the geometries ( https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html ). - However adding the color information for each point increases the memory - footprint. + Having the colors in the material needs less memory and avoids updates + of the geometry only because of recoloring. The default setting is to use colored points where possible. Note, that - this is what is also done in the Qt/Quick code. + this is what is also done in the Qt/Quick classes. */ PreferColoredGeometry = 1 }; From c9f7bf59eee3c1ed5b09b7ac3552bd9f647d4b34 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 23 Sep 2024 15:55:00 +0200 Subject: [PATCH 2/3] better detection of the dirty flags --- src/nodes/QskBoxNode.cpp | 43 +++++++++-------- src/nodes/QskBoxRectangleNode.cpp | 76 ++++++++++++++++++++----------- 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/nodes/QskBoxNode.cpp b/src/nodes/QskBoxNode.cpp index 5ed2a0cd..a4ea7f74 100644 --- a/src/nodes/QskBoxNode.cpp +++ b/src/nodes/QskBoxNode.cpp @@ -67,31 +67,34 @@ void QskBoxNode::updateNode( const QRectF& rect, QskBoxRectangleNode* rectNode = nullptr; QskBoxRectangleNode* fillNode = nullptr; - if ( !shadowMetrics.isNull() - && shadowColor.isValid() && shadowColor.alpha() != 0 ) + if ( !rect.isEmpty() ) { - shadowNode = qskNode< QskBoxShadowNode >( this, ShadowRole ); - shadowNode->setShadowData( shadowMetrics.shadowRect( rect ), - shape, shadowMetrics.blurRadius(), shadowColor ); - } - - if ( QskBoxRectangleNode::isCombinedGeometrySupported( gradient ) ) - { - rectNode = qskNode< QskBoxRectangleNode >( this, BoxRole ); - rectNode->updateBox( rect, shape, borderMetrics, borderColors, gradient ); - } - else - { - if ( !borderMetrics.isNull() && borderColors.isVisible() ) + if ( !shadowMetrics.isNull() + && shadowColor.isValid() && shadowColor.alpha() != 0 ) { - rectNode = qskNode< QskBoxRectangleNode >( this, BoxRole ); - rectNode->updateBorder( rect, shape, borderMetrics, borderColors ); + shadowNode = qskNode< QskBoxShadowNode >( this, ShadowRole ); + shadowNode->setShadowData( shadowMetrics.shadowRect( rect ), + shape, shadowMetrics.blurRadius(), shadowColor ); } - if ( gradient.isVisible() ) + if ( QskBoxRectangleNode::isCombinedGeometrySupported( gradient ) ) { - fillNode = qskNode< QskBoxRectangleNode >( this, FillRole ); - fillNode->updateFilling( rect, shape, borderMetrics, gradient ); + rectNode = qskNode< QskBoxRectangleNode >( this, BoxRole ); + rectNode->updateBox( rect, shape, borderMetrics, borderColors, gradient ); + } + else + { + if ( !borderMetrics.isNull() && borderColors.isVisible() ) + { + rectNode = qskNode< QskBoxRectangleNode >( this, BoxRole ); + rectNode->updateBorder( rect, shape, borderMetrics, borderColors ); + } + + if ( gradient.isVisible() ) + { + fillNode = qskNode< QskBoxRectangleNode >( this, FillRole ); + fillNode->updateFilling( rect, shape, borderMetrics, gradient ); + } } } diff --git a/src/nodes/QskBoxRectangleNode.cpp b/src/nodes/QskBoxRectangleNode.cpp index ec8b2a7e..b7e519f9 100644 --- a/src/nodes/QskBoxRectangleNode.cpp +++ b/src/nodes/QskBoxRectangleNode.cpp @@ -12,6 +12,12 @@ #include "QskGradientDirection.h" #include "QskFillNodePrivate.h" +static inline bool qskHasBorder( + const QskBoxBorderMetrics& metrics, const QskBoxBorderColors& colors ) +{ + return !metrics.isNull() && colors.isVisible(); +} + class QskBoxRectangleNodePrivate final : public QskFillNodePrivate { public: @@ -21,7 +27,7 @@ class QskBoxRectangleNodePrivate final : public QskFillNodePrivate node->resetGeometry(); } - bool updateMetrics( const QRectF& rect, + inline bool updateMetrics( const QRectF& rect, const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics ) { QskHashValue hash = 13000; @@ -30,10 +36,11 @@ class QskBoxRectangleNodePrivate final : public QskFillNodePrivate hash = shape.hash( hash ); hash = borderMetrics.hash( hash ); - return updateValue( m_metricsHash, hash ); + return updateHash( m_metricsHash, hash ); } - bool updateColors( const QskBoxBorderColors& borderColors, const QskGradient& gradient ) + inline bool updateColors( + const QskBoxBorderColors& borderColors, const QskGradient& gradient ) { QskHashValue hash = 13000; @@ -43,11 +50,11 @@ class QskBoxRectangleNodePrivate final : public QskFillNodePrivate if ( gradient.isVisible() ) hash = gradient.hash( hash ); - return updateValue( m_colorsHash, hash ); + return updateHash( m_colorsHash, hash ); } private: - inline bool updateValue( QskHashValue& value, const QskHashValue newValue ) const + inline bool updateHash( QskHashValue& value, const QskHashValue newValue ) const { if ( newValue != value ) { @@ -102,11 +109,13 @@ void QskBoxRectangleNode::updateFilling( const QRectF& rect, const bool coloredGeometry = hasHint( PreferColoredGeometry ) && QskBoxRenderer::isGradientSupported( fillGradient ); - const bool dirtyMetrics = d->updateMetrics( rect, shape, borderMetrics ); - const bool dirtyColors = d->updateColors( QskBoxBorderColors(), fillGradient ) - && ( coloredGeometry == isGeometryColored() ); + bool dirtyGeometry = d->updateMetrics( rect, shape, borderMetrics ); + bool dirtyMaterial = d->updateColors( QskBoxBorderColors(), fillGradient ); - if ( dirtyMetrics || dirtyColors ) + if ( coloredGeometry != isGeometryColored() ) + dirtyGeometry = dirtyMaterial = true; + + if ( dirtyGeometry || dirtyMaterial ) { if ( coloredGeometry ) { @@ -121,16 +130,15 @@ void QskBoxRectangleNode::updateFilling( const QRectF& rect, { if ( fillGradient.isMonochrome() ) { - if ( dirtyColors ) + if ( dirtyMaterial ) setColoring( fillGradient.rgbStart() ); } else { - // dirtyMetrics: the shader also depends on rect ! setColoring( rect, fillGradient ); } - if ( dirtyMetrics ) + if ( dirtyGeometry ) { QskBoxRenderer::setFillLines( rect, shape, borderMetrics, *geometry() ); @@ -147,28 +155,44 @@ void QskBoxRectangleNode::updateBorder( const QRectF& rect, { Q_D( QskBoxRectangleNode ); - if ( rect.isEmpty() || borderMetrics.isNull() || !borderColors.isVisible() ) + if ( rect.isEmpty() || !qskHasBorder( borderMetrics, borderColors ) ) { d->resetNode( this ); return; } - const bool dirtyMetrics = d->updateMetrics( rect, shape, borderMetrics ); - const bool dirtyColors = d->updateColors( borderColors, QskGradient() ); + const bool coloredGeometry = hasHint( PreferColoredGeometry ) + || !borderColors.isMonochrome(); - if ( dirtyMetrics || dirtyColors ) + bool dirtyGeometry = d->updateMetrics( rect, shape, borderMetrics ); + bool dirtyMaterial = d->updateColors( borderColors, QskGradient() ); + + if ( coloredGeometry != isGeometryColored() ) + dirtyGeometry = dirtyMaterial = true; + + if ( dirtyGeometry || dirtyMaterial ) { - const auto coloring = QskFillNode::Polychrome; + if ( coloredGeometry ) + { + setColoring( QskFillNode::Polychrome ); - if ( coloring == QskFillNode::Polychrome ) - setColoring( coloring ); + QskBoxRenderer::setColoredBorderLines( rect, shape, + borderMetrics, borderColors, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } else + { setColoring( borderColors.left().rgbStart() ); - QskBoxRenderer::setColoredBorderLines( rect, shape, borderMetrics, - borderColors, *this->geometry() ); + if ( dirtyGeometry ) + { + QskBoxRenderer::setBorderLines( rect, shape, + borderMetrics, *geometry() ); - markDirty( QSGNode::DirtyGeometry ); + markDirty( QSGNode::DirtyGeometry ); + } + } } } @@ -185,14 +209,14 @@ void QskBoxRectangleNode::updateBox( const QRectF& rect, } const bool hasFill = gradient.isVisible(); - const bool hasBorder = !borderMetrics.isNull() && borderColors.isVisible(); + const bool hasBorder = qskHasBorder( borderMetrics, borderColors ); if ( hasFill && hasBorder ) { - const bool dirtyMetrics = d->updateMetrics( rect, shape, borderMetrics ); - const bool dirtyColors = d->updateColors( borderColors, gradient ); + const bool isDirty = d->updateMetrics( rect, shape, borderMetrics ) + || d->updateColors( borderColors, gradient ) || !isGeometryColored(); - if ( dirtyMetrics || dirtyColors ) + if ( isDirty ) { /* For monochrome border/fiiling with the same color we might be From b69f84e865a85c3955a028e45da6b5f3ba770e3f Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 23 Sep 2024 16:04:09 +0200 Subject: [PATCH 3/3] handling of arcs improved --- playground/charts/CircularChartSkinlet.cpp | 2 +- src/nodes/QskArcNode.cpp | 149 ++++++------ src/nodes/QskArcRenderNode.cpp | 253 +++++++++++++++------ src/nodes/QskArcRenderNode.h | 8 +- src/nodes/QskArcRenderer.cpp | 75 ++++-- src/nodes/QskArcRenderer.h | 18 +- 6 files changed, 344 insertions(+), 161 deletions(-) diff --git a/playground/charts/CircularChartSkinlet.cpp b/playground/charts/CircularChartSkinlet.cpp index efdabda4..f78e228a 100644 --- a/playground/charts/CircularChartSkinlet.cpp +++ b/playground/charts/CircularChartSkinlet.cpp @@ -199,7 +199,7 @@ QSGNode* CircularChartSkinlet::updateArcSegmentNode( const QskSkinnable*, if ( arcNode == nullptr ) arcNode = new QskArcRenderNode(); - arcNode->updateNode( m_data->closedArcRect, metrics, true, + arcNode->updateArc( m_data->closedArcRect, metrics, true, borderWidth, borderColor, gradient ); return arcNode; diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index c2dc3f7a..d8328bd7 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -10,7 +10,6 @@ #include "QskArcRenderer.h" #include "QskMargins.h" #include "QskGradient.h" -#include "QskShapeNode.h" #include "QskSGNode.h" #include "QskShadowMetrics.h" @@ -22,40 +21,38 @@ namespace { ShadowRole, - PathRole, - ArcRole + /* + If possible border + filling will be displayed by ArcRole + Otherwise ArcRole displays the border and FillRole the filling + */ + + ArcRole, + FillRole }; } -static inline QskGradient qskEffectiveGradient( - const QskGradient& gradient, const QskArcMetrics& metrics ) +static void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node ) { - if ( !gradient.isMonochrome() ) - { - if ( gradient.type() == QskGradient::Stops ) - { - QskGradient g( gradient.stops() ); - g.setConicDirection( 0.5, 0.5, metrics.startAngle(), 360.0 ); + static const QVector< quint8 > roles = { ShadowRole, ArcRole, FillRole }; - return g; - } - } - - return gradient; + auto oldNode = QskSGNode::findChildNode( parentNode, role ); + QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node ); } template< typename Node > -inline Node* qskInsertOrRemoveNode( QSGNode* parentNode, quint8 role, bool isValid ) +inline Node* qskNode( QSGNode* parentNode, quint8 role ) { using namespace QskSGNode; - Node* oldNode = static_cast< Node* >( findChildNode( parentNode, role ) ); - Node* newNode = isValid ? ensureNode< Node >( oldNode ) : nullptr; + auto node = static_cast< Node* > ( findChildNode( parentNode, role ) ); - static const QVector< quint8 > roles = { ShadowRole, PathRole, ArcRole }; - replaceChildNode( roles, role, parentNode, oldNode, newNode ); + if ( node == nullptr ) + { + node = new Node(); + setNodeRole( node, role ); + } - return newNode; + return node; } QskArcNode::QskArcNode() @@ -82,60 +79,68 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient, const QColor& shadowColor, const QskShadowMetrics& shadowMetrics ) { - const bool radial = false; + using namespace QskSGNode; - const auto metricsArc = arcMetrics.toAbsolute( rect.size() ); + QskArcShadowNode* shadowNode = nullptr; + QskArcRenderNode* arcNode = nullptr; + QskArcRenderNode* fillNode = nullptr; - if ( metricsArc.isNull() || rect.isEmpty() ) + if ( !( rect.isEmpty() || arcMetrics.isNull() ) ) { - delete QskSGNode::findChildNode( this, ShadowRole ); - delete QskSGNode::findChildNode( this, PathRole ); - delete QskSGNode::findChildNode( this, ArcRole ); + const bool radial = false; + const auto metricsArc = arcMetrics.toAbsolute( rect.size() ); - return; + const auto hasFilling = gradient.isVisible(); + const auto hasBorder = ( borderWidth > 0.0 ) + && borderColor.isValid() && ( borderColor.alpha() > 0 ); + + const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 ); + + if ( hasShadow && hasFilling ) + { + /* + The shader of the shadow node is for circular arcs and we have some + unwanted scaling issues for the spread/blur values when having ellipsoid + arcs. We might also want to add the spread value to the ends of the arc + and not only to its radius. TODO ... + */ + + shadowNode = qskNode< QskArcShadowNode >( this, ShadowRole ); + + const auto sm = shadowMetrics.toAbsolute( rect.size() ); + const auto shadowRect = sm.shadowRect( rect ); + const auto spreadRadius = sm.spreadRadius() + 0.5 * metricsArc.thickness(); + + shadowNode->setShadowData( shadowRect, spreadRadius, sm.blurRadius(), + metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor ); + } + + if ( hasBorder || hasFilling ) + { + arcNode = qskNode< QskArcRenderNode >( this, ArcRole ); + + if ( hasBorder && hasFilling ) + { + if ( !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) ) + fillNode = qskNode< QskArcRenderNode >( this, FillRole ); + } + + if ( fillNode ) + { + arcNode->updateBorder( rect, metricsArc, + radial, borderWidth, borderColor ); + + fillNode->updateFilling( rect, metricsArc, radial, borderWidth, gradient ); + } + else + { + arcNode->updateArc( rect, metricsArc, + radial, borderWidth, borderColor, gradient ); + } + } } - const auto hasFilling = gradient.isVisible(); - const auto hasBorder = ( borderWidth > 0.0 ) - && borderColor.isValid() && ( borderColor.alpha() > 0 ); - const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 ); - - auto shadowNode = qskInsertOrRemoveNode< QskArcShadowNode >( - this, ShadowRole, hasFilling && hasShadow ); - - if ( shadowNode ) - { - /* - The shader of the shadow node is for circular arcs and we have some - unwanted scaling issues for the spread/blur values when having ellipsoid - arcs. We might also want to add the spread value to the ends of the arc - and not only to its radius. TODO ... - */ - - const auto sm = shadowMetrics.toAbsolute( rect.size() ); - const auto shadowRect = sm.shadowRect( rect ); - const auto spreadRadius = sm.spreadRadius() + 0.5 * metricsArc.thickness(); - - shadowNode->setShadowData( shadowRect, spreadRadius, sm.blurRadius(), - metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor ); - } - - auto pathNode = qskInsertOrRemoveNode< QskShapeNode >( this, PathRole, - hasFilling && !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) ); - - if ( pathNode ) - { - const auto path = metricsArc.painterPath( rect, radial ); - pathNode->updateNode( path, QTransform(), rect, - qskEffectiveGradient( gradient, metricsArc ) ); - } - - auto arcNode = qskInsertOrRemoveNode< QskArcRenderNode >( - this, ArcRole, hasBorder || ( hasFilling && !pathNode ) ); - - if ( arcNode ) - { - arcNode->updateNode( rect, metricsArc, radial, - borderWidth, borderColor, pathNode ? QskGradient() : gradient ); - } + qskUpdateChildren( this, ShadowRole, shadowNode ); + qskUpdateChildren( this, ArcRole, arcNode ); + qskUpdateChildren( this, FillRole, fillNode ); } diff --git a/src/nodes/QskArcRenderNode.cpp b/src/nodes/QskArcRenderNode.cpp index 0d7ffddd..4da98336 100644 --- a/src/nodes/QskArcRenderNode.cpp +++ b/src/nodes/QskArcRenderNode.cpp @@ -11,12 +11,61 @@ #include "QskSGNode.h" #include "QskFillNodePrivate.h" +static inline bool qskHasBorder( qreal width, const QColor& color ) +{ + return ( width > 0.0 ) && color.isValid() && ( color.alpha() > 0 ); +} + class QskArcRenderNodePrivate final : public QskFillNodePrivate { public: - inline void resetValues() { hash = 0; } + inline void resetNode( QskArcRenderNode* node ) + { + m_metricsHash = m_colorsHash = 0; + node->resetGeometry(); + } - QskHashValue hash = 0; + inline bool updateMetrics( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth ) + { + QskHashValue hash = 13000; + + hash = qHashBits( &rect, sizeof( rect ), hash ); + hash = metrics.hash( hash ); + hash = qHash( radial, hash ); + hash = qHash( borderWidth, hash ); + + return updateHash( m_metricsHash, hash ); + } + + inline bool updateColors( const QColor& borderColor, const QskGradient& gradient ) + { + QskHashValue hash = 13000; + + if ( borderColor.isValid() && ( borderColor.alpha() > 0 ) ) + hash = qHashBits( &borderColor, sizeof( borderColor ), hash ); + + if ( gradient.isVisible() ) + hash = gradient.hash( hash ); + + return updateHash( m_colorsHash, hash ); + } + + private: + inline bool updateHash( QskHashValue& value, const QskHashValue newValue ) const + { + if ( newValue != value ) + { + value = newValue; + return true; + } + + return false; + } + + public: + QskHashValue m_metricsHash = 0; + QskHashValue m_colorsHash = 0; }; QskArcRenderNode::QskArcRenderNode() @@ -28,94 +77,168 @@ QskArcRenderNode::~QskArcRenderNode() { } -void QskArcRenderNode::updateNode( const QRectF& rect, +void QskArcRenderNode::updateFilling( const QRectF& rect, const QskArcMetrics& metrics, const QskGradient& gradient ) { - updateNode( rect, metrics, false, 0.0, QColor(), gradient ); + updateFilling( rect, metrics, false, 0.0, gradient ); } -void QskArcRenderNode::updateNode( const QRectF& rect, - const QskArcMetrics& metrics, qreal borderWidth, const QColor& borderColor ) +void QskArcRenderNode::updateFilling( const QRectF& rect, + const QskArcMetrics& arcMetrics, bool radial, + qreal borderWidth, const QskGradient& gradient ) { - updateNode( rect, metrics, false, borderWidth, borderColor, QskGradient() ); + Q_D( QskArcRenderNode ); + + if ( rect.isEmpty() || arcMetrics.isNull() || !gradient.isVisible() ) + { + d->resetNode( this ); + return; + } + + const auto metrics = arcMetrics.toAbsolute( rect.size() ); + + if ( borderWidth >= 0.5 * metrics.thickness() ) + { + d->resetNode( this ); + return; + } + + const bool coloredGeometry = hasHint( PreferColoredGeometry ) + && QskArcRenderer::isGradientSupported( rect, metrics, gradient ); + + bool dirtyGeometry = d->updateMetrics( rect, metrics, radial, borderWidth ); + bool dirtyMaterial = d->updateColors( QColor(), gradient ); + + if ( coloredGeometry != isGeometryColored() ) + dirtyGeometry = dirtyMaterial = true; + + if ( dirtyGeometry || dirtyMaterial ) + { + if ( coloredGeometry ) + { + setColoring( QskFillNode::Polychrome ); + + QskArcRenderer::setColoredFillLines( rect, metrics, radial, + borderWidth, gradient, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + else + { + setColoring( rect, gradient ); + + if ( dirtyGeometry ) + { + QskArcRenderer::setFillLines( rect, metrics, + radial, borderWidth, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + } + } } -void QskArcRenderNode::updateNode( +void QskArcRenderNode::updateBorder( const QRectF& rect, + const QskArcMetrics& arcMetrics, bool radial, + qreal borderWidth, const QColor& borderColor ) +{ + Q_D( QskArcRenderNode ); + + if ( rect.isEmpty() || arcMetrics.isNull() + || !qskHasBorder( borderWidth, borderColor ) ) + { + d->resetNode( this ); + return; + } + + const bool coloredGeometry = hasHint( PreferColoredGeometry ); + + bool dirtyGeometry = d->updateMetrics( rect, arcMetrics, radial, borderWidth ); + bool dirtyMaterial = d->updateColors( borderColor, QskGradient() ); + + if ( coloredGeometry != isGeometryColored() ) + dirtyGeometry = dirtyMaterial = true; + + if ( dirtyGeometry || dirtyMaterial ) + { + const auto metrics = arcMetrics.toAbsolute( rect.size() ); + borderWidth = qMin( borderWidth, 0.5 * metrics.thickness() ); + + if ( coloredGeometry ) + { + setColoring( QskFillNode::Polychrome ); + + QskArcRenderer::setColoredBorderLines( rect, metrics, radial, + borderWidth, borderColor, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + else + { + setColoring( borderColor ); + + if ( dirtyGeometry ) + { + QskArcRenderer::setBorderLines( rect, metrics, radial, + borderWidth, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + } + } +} + +void QskArcRenderNode::updateArc( const QRectF& rect, const QskArcMetrics& arcMetrics, bool radial, qreal borderWidth, const QColor& borderColor, const QskGradient& gradient ) { Q_D( QskArcRenderNode ); const auto metrics = arcMetrics.toAbsolute( rect.size() ); + if ( rect.isEmpty() || metrics.isNull() ) + { + d->resetNode( this ); + return; + } + const auto borderMax = 0.5 * metrics.thickness(); const bool hasFill = gradient.isVisible() && ( borderWidth < borderMax ); - const bool hasBorder = ( borderWidth > 0.0 ) - && borderColor.isValid() && ( borderColor.alpha() > 0 ); + const bool hasBorder = qskHasBorder( borderWidth, borderColor ); - if ( rect.isEmpty() || metrics.isNull() || !( hasFill || hasBorder ) ) + if ( hasBorder && hasFill ) { - d->resetValues(); - QskSGNode::resetGeometry( this ); + /* + When having a monochrome gradient that has the same color as the + border we might be able to use QskFillNode::Monochrome. For the + moment this is not implemented + */ + borderWidth = qMin( borderWidth, borderMax ); - return; + const bool isDirty = d->updateMetrics( rect, arcMetrics, radial, borderWidth ) + || d->updateColors( borderColor, gradient ) || !isGeometryColored(); + + if ( isDirty ) + { + setColoring( QskFillNode::Polychrome ); + + QskArcRenderer::setColoredBorderAndFillLines( rect, metrics, radial, + borderWidth, borderColor, gradient, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } } - - borderWidth = qMin( borderWidth, borderMax ); - - QskHashValue hash = 3496; - - hash = qHashBits( &rect, sizeof( QRectF ), hash ); - hash = qHash( borderWidth, hash ); - hash = qHashBits( &borderColor, sizeof( borderColor ), hash ); - hash = metrics.hash( hash ); - hash = gradient.hash( hash ); - hash = qHash( radial, hash ); - - if ( hash == d->hash ) - return; - - d->hash = hash; - - auto coloring = QskFillNode::Polychrome; - -#if 0 - if ( !( hasFill && hasBorder ) ) + else if ( hasBorder ) { - if ( hasBorder || ( hasFill && gradient.isMonochrome() ) ) - coloring = QskFillNode::Monochrome; + updateBorder( rect, arcMetrics, radial, borderWidth, borderColor ); } -#endif - - auto& geometry = *this->geometry(); - - if ( coloring == QskFillNode::Polychrome ) + else if ( hasFill ) { - setColoring( coloring ); - - QskArcRenderer::renderArc( rect, metrics, radial, - borderWidth, gradient, borderColor, geometry ); + updateFilling( rect, arcMetrics, radial, borderWidth, gradient ); } else { - if ( hasFill ) - { - setColoring( gradient.rgbStart() ); - - QskArcRenderer::renderArc( rect, metrics, radial, - borderWidth, gradient, borderColor, geometry ); - } - else - { - setColoring( borderColor ); - - QskArcRenderer::renderArc( rect, metrics, radial, - borderWidth, gradient, borderColor, geometry ); - } + d->resetNode( this ); + return; } - - markDirty( QSGNode::DirtyGeometry ); - markDirty( QSGNode::DirtyMaterial ); - - geometry.markVertexDataDirty(); } diff --git a/src/nodes/QskArcRenderNode.h b/src/nodes/QskArcRenderNode.h index f0d248ab..4ae4b2c1 100644 --- a/src/nodes/QskArcRenderNode.h +++ b/src/nodes/QskArcRenderNode.h @@ -22,12 +22,14 @@ class QSK_EXPORT QskArcRenderNode : public QskFillNode QskArcRenderNode(); ~QskArcRenderNode() override; - void updateNode( const QRectF&, const QskArcMetrics&, const QskGradient& ); + void updateFilling( const QRectF&, const QskArcMetrics&, const QskGradient& ); + void updateFilling( const QRectF&, const QskArcMetrics&, bool radial, + qreal borderWidth, const QskGradient& ); - void updateNode( const QRectF&, const QskArcMetrics&, + void updateBorder( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, const QColor& borderColor ); - void updateNode( const QRectF&, const QskArcMetrics&, bool radial, + void updateArc( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, const QColor& borderColor, const QskGradient& ); private: diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp index 1a7963ce..7569939d 100644 --- a/src/nodes/QskArcRenderer.cpp +++ b/src/nodes/QskArcRenderer.cpp @@ -11,7 +11,6 @@ #include "QskRgbValue.h" #include -#include static inline QskVertex::Line* qskAllocateLines( QSGGeometry& geometry, int lineCount ) @@ -466,8 +465,8 @@ bool QskArcRenderer::isGradientSupported( const QRectF& rect, if ( qskFuzzyCompare( direction.aspectRatio(), aspectRatio ) ) { /* - we should be able to create a list of stops from - this gradient that works for the renderer. TODO ... + we should be able to create a list of stops from + this gradient that works for the renderer. TODO ... */ } } @@ -483,17 +482,55 @@ bool QskArcRenderer::isGradientSupported( const QRectF& rect, return false; } -void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, - bool radial, const QskGradient& gradient, QSGGeometry& geometry ) -{ - renderArc( rect, metrics, radial, 0, gradient, QColor(), geometry ); -} - -void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, - bool radial, qreal borderWidth, const QskGradient& gradient, +void QskArcRenderer::setColoredBorderLines( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth, const QColor& borderColor, QSGGeometry& geometry ) { geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); + + if ( borderWidth <= 0.0 || !( borderColor.isValid() && borderColor.alpha() > 0 ) ) + { + qskAllocateColoredLines( geometry, 0 ); + return; + } + + const Renderer renderer( rect, metrics, radial, QskGradient(), borderColor ); + + if ( const auto lines = qskAllocateColoredLines( geometry, renderer.borderCount() ) ) + { + renderer.renderArc( metrics.thickness(), borderWidth, + static_cast< QskVertex::ColoredLine* >( nullptr ), lines ); + } +} + +void QskArcRenderer::setColoredFillLines( const QRectF& rect, const QskArcMetrics& metrics, + bool radial, qreal borderWidth, const QskGradient& gradient, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); + + if ( !gradient.isVisible() ) + { + qskAllocateColoredLines( geometry, 0 ); + return; + } + + const Renderer renderer( rect, metrics, radial, gradient, QColor( 0, 0, 0, 0 ) ); + + if ( const auto lines = qskAllocateColoredLines( geometry, renderer.fillCount() ) ) + { + renderer.renderArc( metrics.thickness(), borderWidth, lines, + static_cast< QskVertex::ColoredLine* >( nullptr ) ); + } +} + +void QskArcRenderer::setColoredBorderAndFillLines( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth, + const QColor& borderColor, const QskGradient& gradient, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); const Renderer renderer( rect, metrics, radial, gradient, borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) ); @@ -523,13 +560,20 @@ void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics } } -void QskArcRenderer::renderBorderGeometry( const QRectF& rect, +void QskArcRenderer::setBorderLines( const QRectF& rect, const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry ) { geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); - + geometry.markVertexDataDirty(); + + if ( borderWidth <= 0.0 ) + { + qskAllocateLines( geometry, 0 ); + return; + } + const Renderer renderer( rect, metrics, radial, QskGradient(), 0 ); - + const auto lines = qskAllocateLines( geometry, renderer.borderCount() ); if ( lines ) { @@ -538,10 +582,11 @@ void QskArcRenderer::renderBorderGeometry( const QRectF& rect, } } -void QskArcRenderer::renderFillGeometry( const QRectF& rect, +void QskArcRenderer::setFillLines( const QRectF& rect, const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry ) { geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); const Renderer renderer( rect, metrics, radial, QskRgb::Black, 0 ); diff --git a/src/nodes/QskArcRenderer.h b/src/nodes/QskArcRenderer.h index 1b0ce528..aa0e40f2 100644 --- a/src/nodes/QskArcRenderer.h +++ b/src/nodes/QskArcRenderer.h @@ -25,24 +25,32 @@ namespace QskArcRenderer - using shaders setting the colors */ - QSK_EXPORT void renderBorderGeometry( const QRectF&, + QSK_EXPORT void setBorderLines( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& ); - QSK_EXPORT void renderFillGeometry( const QRectF&, + QSK_EXPORT void setFillLines( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& ); /* Filling the geometry with color information: see QSGGeometry::defaultAttributes_ColoredPoint2D() + + Usually used in combination with QSGVertexColorMaterial */ QSK_EXPORT bool isGradientSupported( const QRectF&, const QskArcMetrics&, const QskGradient& ); - QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial, - qreal borderWidth, const QskGradient&, const QColor& borderColor, QSGGeometry& ); + QSK_EXPORT void setColoredBorderLines( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, + const QColor& borderColor, QSGGeometry& ); - QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial, + QSK_EXPORT void setColoredFillLines( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, const QskGradient&, QSGGeometry& ); + + QSK_EXPORT void setColoredBorderAndFillLines( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, + const QColor& borderColor, const QskGradient&, QSGGeometry& ); } #endif