QskLinesNode improvements

This commit is contained in:
Uwe Rathmann 2023-05-25 15:03:48 +02:00
parent 3c62809308
commit 587183993b
4 changed files with 317 additions and 96 deletions

View File

@ -21,8 +21,10 @@
#include "QskGradient.h" #include "QskGradient.h"
#include "QskGraphicNode.h" #include "QskGraphicNode.h"
#include "QskGraphic.h" #include "QskGraphic.h"
#include "QskLinesNode.h"
#include "QskRectangleNode.h" #include "QskRectangleNode.h"
#include "QskSGNode.h" #include "QskSGNode.h"
#include "QskStippleMetrics.h"
#include "QskTextColors.h" #include "QskTextColors.h"
#include "QskTextNode.h" #include "QskTextNode.h"
#include "QskTextOptions.h" #include "QskTextOptions.h"
@ -161,6 +163,11 @@ static inline bool qskIsArcVisible( const QskArcMetrics& arcMetrics,
return gradient.isVisible(); 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( static inline QskTextColors qskTextColors(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const QskSkinnable* skinnable, QskAspect::Subcontrol subControl )
{ {
@ -199,13 +206,10 @@ static inline QSGNode* qskUpdateBoxNode(
if ( qskIsBoxVisible( absoluteMetrics, borderColors, gradient ) ) 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 absoluteShape = shape.toAbsolute( size );
const auto absoluteShadowMetrics = shadowMetrics.toAbsolute( size ); const auto absoluteShadowMetrics = shadowMetrics.toAbsolute( size );
auto boxNode = QskSGNode::ensureNode< QskBoxNode >( node );
boxNode->updateNode( rect, absoluteShape, absoluteMetrics, boxNode->updateNode( rect, absoluteShape, absoluteMetrics,
borderColors, gradient, absoluteShadowMetrics, shadowColor ); borderColors, gradient, absoluteShadowMetrics, shadowColor );
@ -226,15 +230,46 @@ static inline QSGNode* qskUpdateArcNode(
if ( !qskIsArcVisible( metrics, borderWidth, borderColor, gradient ) ) if ( !qskIsArcVisible( metrics, borderWidth, borderColor, gradient ) )
return nullptr; return nullptr;
auto arcNode = static_cast< QskArcNode* >( node ); auto arcNode = QskSGNode::ensureNode< QskArcNode >( node );
if ( arcNode == nullptr )
arcNode = new QskArcNode();
arcNode->setArcData( rect, metrics, borderWidth, borderColor, gradient ); arcNode->setArcData( rect, metrics, borderWidth, borderColor, gradient );
return arcNode; 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 class QskSkinlet::PrivateData
{ {
public: public:
@ -575,6 +610,34 @@ QSGNode* QskSkinlet::updateArcNode( const QskSkinnable* skinnable,
borderWidth, borderColor, fillGradient, arcMetrics ); 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* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable,
QSGNode* node, QskAspect::Subcontrol subControl ) const QSGNode* node, QskAspect::Subcontrol subControl ) const
{ {

View File

@ -111,6 +111,12 @@ class QSK_EXPORT QskSkinlet
const QRectF&, const QskGradient&, qreal startAngle, qreal spanAngle, const QRectF&, const QskGradient&, qreal startAngle, qreal spanAngle,
QskAspect::Subcontrol ); 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*, static QSGNode* updateTextNode( const QskSkinnable*, QSGNode*,
const QRectF&, Qt::Alignment, const QString&, QskAspect::Subcontrol ); const QRectF&, Qt::Alignment, const QString&, QskAspect::Subcontrol );

View File

@ -6,6 +6,7 @@
#include "QskLinesNode.h" #include "QskLinesNode.h"
#include "QskVertex.h" #include "QskVertex.h"
#include "QskStippleMetrics.h" #include "QskStippleMetrics.h"
#include "QskStippledLineRenderer.h"
#include "QskSGNode.h" #include "QskSGNode.h"
#include <qsgflatcolormaterial.h> #include <qsgflatcolormaterial.h>
@ -13,10 +14,10 @@
#include <qtransform.h> #include <qtransform.h>
#include <qquickitem.h> #include <qquickitem.h>
#include <qquickwindow.h> #include <qquickwindow.h>
#include <qline.h>
QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_BEGIN
#include <private/qsgnode_p.h> #include <private/qsgnode_p.h>
#include <private/qstroker_p.h>
QSK_QT_PRIVATE_END QSK_QT_PRIVATE_END
namespace namespace
@ -31,85 +32,88 @@ namespace
return t.dy() + t.m22() * y; return t.dy() + t.m22() * y;
} }
/* class Renderer : public QskStippledLineRenderer
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
{ {
public: public:
DashStroker( const QskStippleMetrics& metrics ) inline Renderer( const QskStippleMetrics& metrics )
: QDashStroker( nullptr ) : 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 ) qreal x1, qreal y1, qreal x2, qreal y2 )
{ {
setMoveToHook( addPoint );
setLineToHook( addPoint );
m_points = points; m_points = points;
renderLine( x1, y1, x2, y2 );
begin( this );
m_elements.add( { QPainterPath::MoveToElement, x1, y1 } );
m_elements.add( { QPainterPath::LineToElement, x2, y2 } );
processCurrentSubpath();
end();
return m_points; 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: 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 ); m_points++->set( x1, y1 );
( stroker->m_points++ )->set( x, y ); 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; 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 class QskLinesNodePrivate final : public QSGGeometryNodePrivate
{ {
public: public:
@ -122,6 +126,9 @@ class QskLinesNodePrivate final : public QSGGeometryNodePrivate
inline qreal round( bool isHorizontal, qreal v ) const inline qreal round( bool isHorizontal, qreal v ) const
{ {
if ( !doRound )
return v;
const auto r2 = 2.0 * devicePixelRatio; const auto r2 = 2.0 * devicePixelRatio;
const qreal v0 = isHorizontal ? p0.x() : p0.y(); const qreal v0 = isHorizontal ? p0.x() : p0.y();
@ -131,6 +138,19 @@ class QskLinesNodePrivate final : public QSGGeometryNodePrivate
return f / devicePixelRatio - v0; 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; QSGGeometry geometry;
QSGFlatColorMaterial material; QSGFlatColorMaterial material;
@ -139,7 +159,9 @@ class QskLinesNodePrivate final : public QSGGeometryNodePrivate
qreal devicePixelRatio = 1.0; qreal devicePixelRatio = 1.0;
QskHashValue hash = 0.0; QskHashValue hash = 0.0;
bool dirty = true; bool dirty = true;
bool doRound = false;
}; };
QskLinesNode::QskLinesNode() QskLinesNode::QskLinesNode()
@ -176,6 +198,12 @@ void QskLinesNode::setGlobalPosition(
{ {
Q_D( QskLinesNode ); Q_D( QskLinesNode );
if ( d->doRound == false )
{
d->doRound = true;
d->dirty = true;
}
if ( pos != d->p0 || devicePixelRatio != d->devicePixelRatio ) if ( pos != d->p0 || devicePixelRatio != d->devicePixelRatio )
{ {
d->p0 = pos; 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, qreal lineWidth, const QskStippleMetrics& stippleMetrics,
const QTransform& transform, const QRectF& rect ) const QTransform& transform, const QRectF& rect )
{ {
// using QVarLengthArray instead. TODO ... // using QVarLengthArray instead. TODO ...
updateLines( color, lineWidth, stippleMetrics, transform, updateGrid( color, lineWidth, stippleMetrics, transform,
rect, { rect.left(), rect.right() }, { rect.top(), rect.bottom() } ); 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, 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, qreal lineWidth, const QskStippleMetrics& stippleMetrics,
const QTransform& transform, const QRectF& rect, const QTransform& transform, const QRectF& rect,
const QVector< qreal >& xValues, const QVector< qreal >& yValues ) const QVector< qreal >& xValues, const QVector< qreal >& yValues )
{ {
Q_D( QskLinesNode ); Q_D( QskLinesNode );
if ( color != d->material.color() ) if ( !stippleMetrics.isValid() || !color.isValid() || color.alpha() == 0 )
{ {
d->material.setColor( color ); QskSGNode::resetGeometry( this );
markDirty( QSGNode::DirtyMaterial ); return;
} }
QskHashValue hash = 9784; QskHashValue hash = 9784;
@ -223,24 +321,62 @@ void QskLinesNode::updateLines( const QColor& color,
if( d->dirty ) if( d->dirty )
{ {
if ( rect.isEmpty() || !stippleMetrics.isValid() updateGeometry( stippleMetrics, transform, rect, xValues, yValues );
|| !color.isValid() || color.alpha() == 0 )
{
QskSGNode::resetGeometry( this );
}
else
{
updateGeometry( stippleMetrics,
transform, rect, xValues, yValues );
}
markDirty( QSGNode::DirtyGeometry ); markDirty( QSGNode::DirtyGeometry );
d->dirty = false; d->dirty = false;
} }
const float lineWidthF = lineWidth; d->setLineAttributes( this, color, lineWidth );
if( lineWidthF != d->geometry.lineWidth() ) }
d->geometry.setLineWidth( lineWidthF );
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( void QskLinesNode::updateGeometry(
@ -275,12 +411,13 @@ void QskLinesNode::updateGeometry(
} }
else else
{ {
DashStroker stroker( stippleMetrics ); Renderer renderer( stippleMetrics );
const int countX = stroker.pointCount( 0.0, y1, 0.0, y2 ); const auto countX = renderer.dashCount( 0.0, y1, 0.0, y2 );
const int countY = stroker.pointCount( x1, 0.0, x2, 0.0 ); 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 = d->geometry.vertexDataAsPoint2D();
points = setStippledLines( Qt::Vertical, y1, y2, points = setStippledLines( Qt::Vertical, y1, y2,
@ -305,7 +442,7 @@ QSGGeometry::Point2D* QskLinesNode::setStippledLines(
if ( count <= 0 ) if ( count <= 0 )
return points; return points;
DashStroker stroker( stippleMetrics ); Renderer renderer( stippleMetrics );
// Calculating the dashes for the first line // Calculating the dashes for the first line
@ -317,7 +454,7 @@ QSGGeometry::Point2D* QskLinesNode::setStippledLines(
auto x = mapX( transform, values[0] ); auto x = mapX( transform, values[0] );
x = d->round( true, x ); x = d->round( true, x );
points = stroker.addDashes( points, x, v1, x, v2 ); points = renderer.addDashes( points, x, v1, x, v2 );
dashCount = points - line0; dashCount = points - line0;
} }
else else
@ -325,7 +462,7 @@ QSGGeometry::Point2D* QskLinesNode::setStippledLines(
auto y = mapY( transform, values[0] ); auto y = mapY( transform, values[0] );
y = d->round( false, y ); y = d->round( false, y );
points = stroker.addDashes( points, v1, y, v2, y ); points = renderer.addDashes( points, v1, y, v2, y );
dashCount = points - line0; dashCount = points - line0;
} }

View File

@ -15,6 +15,7 @@ class QskIntervalF;
class QskStippleMetrics; class QskStippleMetrics;
class QTransform; class QTransform;
class QPointF; class QPointF;
class QLineF;
class QQuickItem; class QQuickItem;
class QskLinesNodePrivate; class QskLinesNodePrivate;
@ -31,15 +32,29 @@ class QSK_EXPORT QskLinesNode : public QSGGeometryNode
void setGlobalPosition( const QPointF&, qreal devicePixelRatio ); void setGlobalPosition( const QPointF&, qreal devicePixelRatio );
void setGlobalPosition( const QQuickItem* ); 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 QskStippleMetrics&, const QTransform&, const QRectF&,
const QVector< qreal >&, const QVector< qreal >& ); const QVector< qreal >&, const QVector< qreal >& );
void updateLines( const QColor&, qreal lineWidth, void updateRect( const QColor&, qreal lineWidth,
const QskStippleMetrics&, const QTransform&, const QRectF& ); 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: 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( void updateGeometry(
const QskStippleMetrics&, const QTransform&, const QRectF&, const QskStippleMetrics&, const QTransform&, const QRectF&,
const QVector< qreal >&, const QVector< qreal >& ); const QVector< qreal >&, const QVector< qreal >& );