From 587183993badfa84a262cd206c0d3f4b1f04f816 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Thu, 25 May 2023 15:03:48 +0200 Subject: [PATCH] QskLinesNode improvements --- src/controls/QskSkinlet.cpp | 79 ++++++++- src/controls/QskSkinlet.h | 6 + src/nodes/QskLinesNode.cpp | 309 ++++++++++++++++++++++++++---------- src/nodes/QskLinesNode.h | 19 ++- 4 files changed, 317 insertions(+), 96 deletions(-) diff --git a/src/controls/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index a8a194f5..b1e73ace 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -21,8 +21,10 @@ #include "QskGradient.h" #include "QskGraphicNode.h" #include "QskGraphic.h" +#include "QskLinesNode.h" #include "QskRectangleNode.h" #include "QskSGNode.h" +#include "QskStippleMetrics.h" #include "QskTextColors.h" #include "QskTextNode.h" #include "QskTextOptions.h" @@ -161,6 +163,11 @@ static inline bool qskIsArcVisible( const QskArcMetrics& arcMetrics, return gradient.isVisible(); } +static inline bool qskIsLineVisible( const QColor& lineColor, qreal lineWidth ) +{ + return ( lineWidth > 0.0 ) && lineColor.isValid() && ( lineColor.alpha() > 0 ); +} + static inline QskTextColors qskTextColors( const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) { @@ -199,13 +206,10 @@ static inline QSGNode* qskUpdateBoxNode( if ( qskIsBoxVisible( absoluteMetrics, borderColors, gradient ) ) { - auto boxNode = static_cast< QskBoxNode* >( node ); - if ( boxNode == nullptr ) - boxNode = new QskBoxNode(); - const auto absoluteShape = shape.toAbsolute( size ); const auto absoluteShadowMetrics = shadowMetrics.toAbsolute( size ); + auto boxNode = QskSGNode::ensureNode< QskBoxNode >( node ); boxNode->updateNode( rect, absoluteShape, absoluteMetrics, borderColors, gradient, absoluteShadowMetrics, shadowColor ); @@ -226,15 +230,46 @@ static inline QSGNode* qskUpdateArcNode( if ( !qskIsArcVisible( metrics, borderWidth, borderColor, gradient ) ) return nullptr; - auto arcNode = static_cast< QskArcNode* >( node ); - if ( arcNode == nullptr ) - arcNode = new QskArcNode(); - + auto arcNode = QskSGNode::ensureNode< QskArcNode >( node ); arcNode->setArcData( rect, metrics, borderWidth, borderColor, gradient ); return arcNode; } +static inline QSGNode* qskUpdateLineNode( + const QskSkinnable*, QSGNode* node, const QColor& lineColor, + qreal lineWidth, QskStippleMetrics& lineStipple, const QLineF& line ) +{ + if ( line.isNull() ) + return nullptr; + + if ( !qskIsLineVisible( lineColor, lineWidth ) ) + return nullptr; + + auto linesNode = QskSGNode::ensureNode< QskLinesNode >( node ); + linesNode->updateLine( lineColor, lineWidth, lineStipple, + QTransform(), line.p1(), line.p2() ); + + return linesNode; +} + +static inline QSGNode* qskUpdateLinesNode( + const QskSkinnable*, QSGNode* node, const QColor& lineColor, + qreal lineWidth, QskStippleMetrics& lineStipple, const QVector< QLineF >& lines ) +{ + if ( lines.isEmpty() ) + return nullptr; + + if ( !qskIsLineVisible( lineColor, lineWidth ) ) + return nullptr; + + auto linesNode = QskSGNode::ensureNode< QskLinesNode >( node ); + linesNode->updateLines( lineColor, lineWidth, lineStipple, + QTransform(), lines ); + + return linesNode; +} + class QskSkinlet::PrivateData { public: @@ -575,6 +610,34 @@ QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable, borderWidth, borderColor, fillGradient, arcMetrics ); } +QSGNode* QskSkinlet::updateLineNode( const QskSkinnable* skinnable, + QSGNode* node, const QLineF& line, QskAspect::Subcontrol subControl ) +{ + auto lineStipple = skinnable->stippleMetricsHint( subControl ); + if ( !lineStipple.isValid() ) + lineStipple = Qt::SolidLine; + + const auto lineWidth = skinnable->metric( subControl | QskAspect::Size ); + const auto lineColor = skinnable->color( subControl ); + + return qskUpdateLineNode( skinnable, node, + lineColor, lineWidth, lineStipple, line ); +} + +QSGNode* QskSkinlet::updateLinesNode( const QskSkinnable* skinnable, + QSGNode* node, const QVector< QLineF >& lines, QskAspect::Subcontrol subControl ) +{ + auto lineStipple = skinnable->stippleMetricsHint( subControl ); + if ( !lineStipple.isValid() ) + lineStipple = Qt::SolidLine; + + const auto lineWidth = skinnable->metric( subControl | QskAspect::Size ); + const auto lineColor = skinnable->color( subControl ); + + return qskUpdateLinesNode( skinnable, node, + lineColor, lineWidth, lineStipple, lines ); +} + QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable, QSGNode* node, QskAspect::Subcontrol subControl ) const { diff --git a/src/controls/QskSkinlet.h b/src/controls/QskSkinlet.h index d6476363..44291712 100644 --- a/src/controls/QskSkinlet.h +++ b/src/controls/QskSkinlet.h @@ -111,6 +111,12 @@ class QSK_EXPORT QskSkinlet const QRectF&, const QskGradient&, qreal startAngle, qreal spanAngle, QskAspect::Subcontrol ); + static QSGNode* updateLineNode( const QskSkinnable*, QSGNode*, + const QLineF&, QskAspect::Subcontrol ); + + static QSGNode* updateLinesNode( const QskSkinnable*, + QSGNode*, const QVector< QLineF >&, QskAspect::Subcontrol ); + static QSGNode* updateTextNode( const QskSkinnable*, QSGNode*, const QRectF&, Qt::Alignment, const QString&, QskAspect::Subcontrol ); diff --git a/src/nodes/QskLinesNode.cpp b/src/nodes/QskLinesNode.cpp index 3774963d..7d2d2f7c 100644 --- a/src/nodes/QskLinesNode.cpp +++ b/src/nodes/QskLinesNode.cpp @@ -6,6 +6,7 @@ #include "QskLinesNode.h" #include "QskVertex.h" #include "QskStippleMetrics.h" +#include "QskStippledLineRenderer.h" #include "QskSGNode.h" #include @@ -13,10 +14,10 @@ #include #include #include +#include QSK_QT_PRIVATE_BEGIN #include -#include QSK_QT_PRIVATE_END namespace @@ -31,85 +32,88 @@ namespace return t.dy() + t.m22() * y; } - /* - Thanks to the hooks of the stroker classes we can make use - of QDashStroker without having to deal with the overhead of - QPainterPaths. But it might be worth to check if this could - be done in a shader. TODO ... - */ - class DashStroker : public QDashStroker + class Renderer : public QskStippledLineRenderer { public: - DashStroker( const QskStippleMetrics& metrics ) - : QDashStroker( nullptr ) + inline Renderer( const QskStippleMetrics& metrics ) + : QskStippledLineRenderer( metrics ) { - setDashOffset( metrics.offset() ); - setDashPattern( metrics.pattern() ); - - m_elements.reserve( 2 ); } - QSGGeometry::Point2D* addDashes( QSGGeometry::Point2D* points, + inline QSGGeometry::Point2D* addDashes( QSGGeometry::Point2D* points, qreal x1, qreal y1, qreal x2, qreal y2 ) { - setMoveToHook( addPoint ); - setLineToHook( addPoint ); - m_points = points; - - begin( this ); - - m_elements.add( { QPainterPath::MoveToElement, x1, y1 } ); - m_elements.add( { QPainterPath::LineToElement, x2, y2 } ); - - processCurrentSubpath(); - - end(); - + renderLine( x1, y1, x2, y2 ); return m_points; } - int pointCount( qreal x1, qreal y1, qreal x2, qreal y2 ) - { - /* - There should be a faster way to calculate the - number of points. TODO ... - */ - setMoveToHook( countPoint ); - setLineToHook( countPoint ); - - m_count = 0; - - begin( this ); - - m_elements.add( { QPainterPath::MoveToElement, x1, y1 } ); - m_elements.add( { QPainterPath::LineToElement, x2, y2 } ); - - processCurrentSubpath(); - - end(); - - return m_count; - } - private: - static void addPoint( qfixed x, qfixed y, void* data ) + void renderDash( qreal x1, qreal y1, qreal x2, qreal y2 ) override { - auto stroker = reinterpret_cast< DashStroker* >( data ); - ( stroker->m_points++ )->set( x, y ); + m_points++->set( x1, y1 ); + m_points++->set( x2, y2 ); } - static void countPoint( qfixed, qfixed, void* data ) - { - auto stroker = reinterpret_cast< DashStroker* >( data ); - stroker->m_count++; - } - - int m_count = 0; QSGGeometry::Point2D* m_points; }; } +static QSGGeometry::Point2D* qskAddDashes( const QTransform& transform, + int count, const QLineF* lines, const QskStippleMetrics& metrics, + QSGGeometry::Point2D* points ) +{ + if ( count <= 0 ) + return points; + + const bool doTransform = !transform.isIdentity(); + + Renderer renderer( metrics ); + + for ( int i = 0; i < count; i++ ) + { + auto p1 = lines[i].p1(); + auto p2 = lines[i].p2(); + + if ( doTransform ) + { + p1 = transform.map( p1 ); + p2 = transform.map( p2 ); + } + + points = renderer.addDashes( points, p1.x(), p1.y(), p2.x(), p2.y() ); + } + + return points; +} + +static QSGGeometry::Point2D* qskAddLines( const QTransform& transform, + int count, const QLineF* lines, QSGGeometry::Point2D* points ) +{ + if ( count <= 0 ) + return points; + + const bool doTransform = !transform.isIdentity(); + + auto vlines = reinterpret_cast< QskVertex::Line* >( points ); + + for ( int i = 0; i < count; i++ ) + { + auto p1 = lines[i].p1(); + auto p2 = lines[i].p2(); + + if ( doTransform ) + { + p1 = transform.map( p1 ); + p2 = transform.map( p2 ); + } + + vlines++->setLine( p1.x(), p1.y(), p2.x(), p2.y() ); + } + + return reinterpret_cast< QSGGeometry::Point2D* >( vlines ); +} + class QskLinesNodePrivate final : public QSGGeometryNodePrivate { public: @@ -122,6 +126,9 @@ class QskLinesNodePrivate final : public QSGGeometryNodePrivate inline qreal round( bool isHorizontal, qreal v ) const { + if ( !doRound ) + return v; + const auto r2 = 2.0 * devicePixelRatio; const qreal v0 = isHorizontal ? p0.x() : p0.y(); @@ -131,6 +138,19 @@ class QskLinesNodePrivate final : public QSGGeometryNodePrivate return f / devicePixelRatio - v0; } + inline void setLineAttributes( QskLinesNode* node, + const QColor& color, float lineWidth ) + { + if ( color != material.color() ) + { + material.setColor( color ); + node->markDirty( QSGNode::DirtyMaterial ); + } + + if( lineWidth != geometry.lineWidth() ) + geometry.setLineWidth( lineWidth ); + } + QSGGeometry geometry; QSGFlatColorMaterial material; @@ -139,7 +159,9 @@ class QskLinesNodePrivate final : public QSGGeometryNodePrivate qreal devicePixelRatio = 1.0; QskHashValue hash = 0.0; + bool dirty = true; + bool doRound = false; }; QskLinesNode::QskLinesNode() @@ -176,6 +198,12 @@ void QskLinesNode::setGlobalPosition( { Q_D( QskLinesNode ); + if ( d->doRound == false ) + { + d->doRound = true; + d->dirty = true; + } + if ( pos != d->p0 || devicePixelRatio != d->devicePixelRatio ) { d->p0 = pos; @@ -185,26 +213,96 @@ void QskLinesNode::setGlobalPosition( } } -void QskLinesNode::updateLines( const QColor& color, +void QskLinesNode::resetGlobalPosition() +{ + Q_D( QskLinesNode ); + + if ( d->doRound == true ) + { + d->doRound = false; + d->dirty = true; + } +} + +void QskLinesNode::updateRect( const QColor& color, qreal lineWidth, const QskStippleMetrics& stippleMetrics, const QTransform& transform, const QRectF& rect ) { // using QVarLengthArray instead. TODO ... - updateLines( color, lineWidth, stippleMetrics, transform, + updateGrid( color, lineWidth, stippleMetrics, transform, rect, { rect.left(), rect.right() }, { rect.top(), rect.bottom() } ); } +void QskLinesNode::updateLine( const QColor& color, + qreal lineWidth, const QskStippleMetrics& stippleMetrics, + const QTransform& transform, const QPointF& p1, const QPointF& p2 ) +{ + if ( p1 == p2 ) + { + updateLines( color, lineWidth, stippleMetrics, transform, 0, nullptr ); + } + else + { + const QLineF line( p1, p2 ); + updateLines( color, lineWidth, stippleMetrics, transform, 1, &line ); + } +} + void QskLinesNode::updateLines( const QColor& color, + qreal lineWidth, const QskStippleMetrics& stippleMetrics, + const QTransform& transform, const QVector< QLineF >& lines ) +{ + updateLines( color, lineWidth, stippleMetrics, + transform, lines.count(), lines.constData() ); +} + +void QskLinesNode::updateLines( const QColor& color, + qreal lineWidth, const QskStippleMetrics& stippleMetrics, + const QTransform& transform, int count, const QLineF* lines ) +{ + Q_D( QskLinesNode ); + + if ( !stippleMetrics.isValid() || !color.isValid() + || color.alpha() == 0 || count == 0 ) + { + QskSGNode::resetGeometry( this ); + return; + } + + QskHashValue hash = 9784; + + hash = stippleMetrics.hash( hash ); + hash = qHash( transform, hash ); + hash = qHashBits( lines, count * sizeof( QLineF ) ); + + if ( hash != d->hash ) + { + d->dirty = true; + d->hash = hash; + } + + if( d->dirty ) + { + updateGeometry( stippleMetrics, transform, count, lines ); + + markDirty( QSGNode::DirtyGeometry ); + d->dirty = false; + } + + d->setLineAttributes( this, color, lineWidth ); +} + +void QskLinesNode::updateGrid( const QColor& color, qreal lineWidth, const QskStippleMetrics& stippleMetrics, const QTransform& transform, const QRectF& rect, const QVector< qreal >& xValues, const QVector< qreal >& yValues ) { Q_D( QskLinesNode ); - if ( color != d->material.color() ) + if ( !stippleMetrics.isValid() || !color.isValid() || color.alpha() == 0 ) { - d->material.setColor( color ); - markDirty( QSGNode::DirtyMaterial ); + QskSGNode::resetGeometry( this ); + return; } QskHashValue hash = 9784; @@ -223,24 +321,62 @@ void QskLinesNode::updateLines( const QColor& color, if( d->dirty ) { - if ( rect.isEmpty() || !stippleMetrics.isValid() - || !color.isValid() || color.alpha() == 0 ) - { - QskSGNode::resetGeometry( this ); - } - else - { - updateGeometry( stippleMetrics, - transform, rect, xValues, yValues ); - } + updateGeometry( stippleMetrics, transform, rect, xValues, yValues ); markDirty( QSGNode::DirtyGeometry ); d->dirty = false; } - const float lineWidthF = lineWidth; - if( lineWidthF != d->geometry.lineWidth() ) - d->geometry.setLineWidth( lineWidthF ); + d->setLineAttributes( this, color, lineWidth ); +} + +void QskLinesNode::updateGeometry( const QskStippleMetrics& stippleMetrics, + const QTransform& transform, int count, const QLineF* lines ) +{ + Q_D( QskLinesNode ); + + auto& geom = d->geometry; + + QSGGeometry::Point2D* points = nullptr; + + if ( stippleMetrics.isSolid() ) + { + using namespace QskVertex; + + geom.allocate( 2 * count ); + points = geom.vertexDataAsPoint2D(); + + points = qskAddLines( transform, count, lines, points ); + } + else + { + const bool doTransform = !transform.isIdentity(); + + Renderer renderer( stippleMetrics ); + + int lineCount = 0; + for ( int i = 0; i < count; i++ ) + { + auto p1 = lines[i].p1(); + auto p2 = lines[i].p2(); + + if ( doTransform ) + { + p1 = transform.map( p1 ); + p2 = transform.map( p2 ); + } + lineCount += renderer.dashCount( p1, p2 ); + } + + d->geometry.allocate( 2 * lineCount ); + points = d->geometry.vertexDataAsPoint2D(); + + points = qskAddDashes( transform, + count, lines, stippleMetrics, points ); + + } + + Q_ASSERT( geom.vertexCount() == ( points - geom.vertexDataAsPoint2D() ) ); } void QskLinesNode::updateGeometry( @@ -275,12 +411,13 @@ void QskLinesNode::updateGeometry( } else { - DashStroker stroker( stippleMetrics ); + Renderer renderer( stippleMetrics ); - const int countX = stroker.pointCount( 0.0, y1, 0.0, y2 ); - const int countY = stroker.pointCount( x1, 0.0, x2, 0.0 ); + const auto countX = renderer.dashCount( 0.0, y1, 0.0, y2 ); + const auto countY = renderer.dashCount( x1, 0.0, x2, 0.0 ); + const auto count = xValues.count() * countX + yValues.count() * countY; - d->geometry.allocate( xValues.count() * countX + yValues.count() * countY ); + d->geometry.allocate( 2 * count ); points = d->geometry.vertexDataAsPoint2D(); points = setStippledLines( Qt::Vertical, y1, y2, @@ -305,7 +442,7 @@ QSGGeometry::Point2D* QskLinesNode::setStippledLines( if ( count <= 0 ) return points; - DashStroker stroker( stippleMetrics ); + Renderer renderer( stippleMetrics ); // Calculating the dashes for the first line @@ -317,7 +454,7 @@ QSGGeometry::Point2D* QskLinesNode::setStippledLines( auto x = mapX( transform, values[0] ); x = d->round( true, x ); - points = stroker.addDashes( points, x, v1, x, v2 ); + points = renderer.addDashes( points, x, v1, x, v2 ); dashCount = points - line0; } else @@ -325,7 +462,7 @@ QSGGeometry::Point2D* QskLinesNode::setStippledLines( auto y = mapY( transform, values[0] ); y = d->round( false, y ); - points = stroker.addDashes( points, v1, y, v2, y ); + points = renderer.addDashes( points, v1, y, v2, y ); dashCount = points - line0; } diff --git a/src/nodes/QskLinesNode.h b/src/nodes/QskLinesNode.h index 65d1f544..0de6f9ca 100644 --- a/src/nodes/QskLinesNode.h +++ b/src/nodes/QskLinesNode.h @@ -15,6 +15,7 @@ class QskIntervalF; class QskStippleMetrics; class QTransform; class QPointF; +class QLineF; class QQuickItem; class QskLinesNodePrivate; @@ -31,15 +32,29 @@ class QSK_EXPORT QskLinesNode : public QSGGeometryNode void setGlobalPosition( const QPointF&, qreal devicePixelRatio ); void setGlobalPosition( const QQuickItem* ); + void resetGlobalPosition(); - void updateLines( const QColor&, qreal lineWidth, + void updateGrid( const QColor&, qreal lineWidth, const QskStippleMetrics&, const QTransform&, const QRectF&, const QVector< qreal >&, const QVector< qreal >& ); - void updateLines( const QColor&, qreal lineWidth, + void updateRect( const QColor&, qreal lineWidth, const QskStippleMetrics&, const QTransform&, const QRectF& ); + void updateLine( const QColor&, qreal lineWidth, + const QskStippleMetrics&, const QTransform&, + const QPointF&, const QPointF& ); + + void updateLines( const QColor&, qreal lineWidth, + const QskStippleMetrics&, const QTransform&, const QVector< QLineF >& ); + private: + void updateLines( const QColor&, qreal lineWidth, const QskStippleMetrics&, + const QTransform&, int count, const QLineF* ); + + void updateGeometry( const QskStippleMetrics&, const QTransform&, + int count, const QLineF* ); + void updateGeometry( const QskStippleMetrics&, const QTransform&, const QRectF&, const QVector< qreal >&, const QVector< qreal >& );