QskArcRenderNode doing border/filling in one node

This commit is contained in:
Uwe Rathmann 2024-07-24 15:00:05 +02:00
parent c715b625fb
commit a54afb5305
4 changed files with 112 additions and 198 deletions

View File

@ -11,21 +11,11 @@
#include "QskMargins.h" #include "QskMargins.h"
#include "QskGradient.h" #include "QskGradient.h"
#include "QskShapeNode.h" #include "QskShapeNode.h"
#include "QskStrokeNode.h"
#include "QskSGNode.h" #include "QskSGNode.h"
#include "QskShadowMetrics.h" #include "QskShadowMetrics.h"
#include <qpainterpath.h> #include <qpainterpath.h>
#define ARC_BORDER_NODE
#ifdef ARC_BORDER_NODE
using BorderNode = QskArcRenderNode;
#else
#include <qpen.h>
using BorderNode = QskStrokeNode;
#endif
namespace namespace
{ {
enum NodeRole enum NodeRole
@ -33,9 +23,7 @@ namespace
ShadowRole, ShadowRole,
PathRole, PathRole,
ArcRole, ArcRole
BorderRole
}; };
} }
@ -56,13 +44,18 @@ static inline QskGradient qskEffectiveGradient(
return gradient; return gradient;
} }
static void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node ) template< typename Node >
inline Node* qskInsertOrRemoveNode( QSGNode* parentNode, quint8 role, bool isValid )
{ {
static const QVector< quint8 > roles = using namespace QskSGNode;
{ ShadowRole, PathRole, ArcRole, BorderRole };
auto oldNode = QskSGNode::findChildNode( parentNode, role ); Node* oldNode = static_cast< Node* >( findChildNode( parentNode, role ) );
QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node ); Node* newNode = isValid ? ensureNode< Node >( oldNode ) : nullptr;
static const QVector< quint8 > roles = { ShadowRole, PathRole, ArcRole };
replaceChildNode( roles, role, parentNode, oldNode, newNode );
return newNode;
} }
QskArcNode::QskArcNode() QskArcNode::QskArcNode()
@ -93,40 +86,25 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
const auto metricsArc = arcMetrics.toAbsolute( rect.size() ); const auto metricsArc = arcMetrics.toAbsolute( rect.size() );
auto shadowNode = static_cast< QskArcShadowNode* >(
QskSGNode::findChildNode( this, ShadowRole ) );
auto pathNode = static_cast< QskShapeNode* >(
QskSGNode::findChildNode( this, PathRole ) );
auto arcNode = static_cast< QskArcRenderNode* >(
QskSGNode::findChildNode( this, ArcRole ) );
auto borderNode = static_cast< BorderNode* >(
QskSGNode::findChildNode( this, BorderRole ) );
if ( metricsArc.isNull() || rect.isEmpty() ) if ( metricsArc.isNull() || rect.isEmpty() )
{ {
delete shadowNode; delete QskSGNode::findChildNode( this, ShadowRole );
delete pathNode; delete QskSGNode::findChildNode( this, PathRole );
delete arcNode; delete QskSGNode::findChildNode( this, ArcRole );
delete borderNode;
return; return;
} }
const auto isFillNodeVisible = gradient.isVisible(); const auto hasFilling = gradient.isVisible();
const auto isBorderNodeVisible = ( borderWidth > 0.0 ) && ( borderColor.alpha() > 0 ); const auto hasBorder = ( borderWidth > 0.0 )
const auto isShadowNodeVisible = isFillNodeVisible && && borderColor.isValid() && ( borderColor.alpha() > 0 );
shadowColor.isValid() && ( shadowColor.alpha() > 0.0 ); const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 );
if ( isShadowNodeVisible ) auto shadowNode = qskInsertOrRemoveNode< QskArcShadowNode >(
this, ShadowRole, hasFilling && hasShadow );
if ( shadowNode )
{ {
if ( shadowNode == nullptr )
{
shadowNode = new QskArcShadowNode;
QskSGNode::setNodeRole( shadowNode, ShadowRole );
}
/* /*
The shader of the shadow node is for circular arcs and we have some 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 unwanted scaling issues for the spread/blur values when having ellipsoid
@ -141,95 +119,23 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
shadowNode->setShadowData( shadowRect, spreadRadius, sm.blurRadius(), shadowNode->setShadowData( shadowRect, spreadRadius, sm.blurRadius(),
metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor ); metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor );
} }
else
auto pathNode = qskInsertOrRemoveNode< QskShapeNode >(
this, PathRole, hasFilling && !QskArcRenderer::isGradientSupported( gradient ) );
if ( pathNode )
{ {
delete shadowNode; const auto path = metricsArc.painterPath( rect, radial );
shadowNode = nullptr; pathNode->updateNode( path, QTransform(), rect,
qskEffectiveGradient( gradient, metricsArc ) );
} }
if ( isFillNodeVisible ) auto arcNode = qskInsertOrRemoveNode< QskArcRenderNode >(
this, ArcRole, hasBorder || ( hasFilling && !pathNode ) );
if ( arcNode )
{ {
if ( QskArcRenderer::isGradientSupported( gradient ) ) arcNode->updateNode( rect, metricsArc, radial,
{ borderWidth, borderColor, pathNode ? QskGradient() : gradient );
delete pathNode;
pathNode = nullptr;
if ( arcNode == nullptr )
{
arcNode = new QskArcRenderNode;
QskSGNode::setNodeRole( arcNode, ArcRole );
}
arcNode->updateNode( rect, metricsArc, radial,
borderWidth, QColor(), gradient );
}
else
{
delete arcNode;
arcNode = nullptr;
if ( pathNode == nullptr )
{
pathNode = new QskShapeNode;
QskSGNode::setNodeRole( pathNode, PathRole );
}
const auto path = metricsArc.painterPath( rect, radial );
pathNode->updateNode( path, QTransform(), rect,
qskEffectiveGradient( gradient, metricsArc ) );
}
} }
else
{
delete pathNode;
pathNode = nullptr;
delete arcNode;
arcNode = nullptr;
}
if ( isBorderNodeVisible )
{
if ( borderNode == nullptr )
{
borderNode = new BorderNode;
QskSGNode::setNodeRole( borderNode, BorderRole );
}
#ifdef ARC_BORDER_NODE
borderNode->updateNode( rect, metricsArc, radial,
borderWidth, borderColor, QskGradient() );
#else
{
/*
Qt centers the border over the boundaries, while we
always want to have the complete borders inside.
So we have to subtract 0.5 * border.
*/
QPen pen( borderColor, borderWidth );
pen.setCapStyle( Qt::SquareCap );
pen.setJoinStyle( Qt::MiterJoin );
const auto b2 = 0.5 * borderWidth;
const auto r = rect.adjusted( b2, b2, -b2, -b2 );
const auto m = QskArcMetrics( metricsArc.startAngle(), metricsArc.spanAngle(),
metricsArc.thickness() - borderWidth );
const auto path = m.painterPath( r, radial );
borderNode->updateNode( path, QTransform(), pen );
}
#endif
}
else
{
delete borderNode;
borderNode = nullptr;
}
qskUpdateChildren( this, ShadowRole, shadowNode );
qskUpdateChildren( this, PathRole, pathNode );
qskUpdateChildren( this, ArcRole, arcNode );
qskUpdateChildren( this, BorderRole, borderNode );
} }

View File

@ -70,13 +70,10 @@ void QskArcRenderNode::updateNode(
const auto metrics = arcMetrics.toAbsolute( rect.size() ); const auto metrics = arcMetrics.toAbsolute( rect.size() );
const auto borderMax = 0.5 * metrics.thickness(); const auto borderMax = 0.5 * metrics.thickness();
const auto hasFilling = gradient.isVisible()
&& ( borderWidth < borderMax );
bool visible = !( rect.isEmpty() || metrics.isNull() ); bool visible = !( rect.isEmpty() || metrics.isNull() );
if ( visible ) if ( visible )
{ {
visible = hasFilling; visible = gradient.isVisible() && ( borderWidth < borderMax );
if ( !visible ) if ( !visible )
{ {
visible = ( borderWidth > 0.0 ) visible = ( borderWidth > 0.0 )
@ -92,11 +89,13 @@ void QskArcRenderNode::updateNode(
return; return;
} }
borderWidth = qMin( borderWidth, borderMax );
QskHashValue hash = 3496; QskHashValue hash = 3496;
hash = qHashBits( &rect, sizeof( QRectF ), hash ); hash = qHashBits( &rect, sizeof( QRectF ), hash );
hash = qHash( borderWidth, hash ); hash = qHash( borderWidth, hash );
hash = qHash( borderColor.rgba(), hash ); hash = qHashBits( &borderColor, sizeof( borderColor ), hash );
hash = metrics.hash( hash ); hash = metrics.hash( hash );
hash = gradient.hash( hash ); hash = gradient.hash( hash );
hash = qHash( radial, hash ); hash = qHash( radial, hash );
@ -105,18 +104,8 @@ void QskArcRenderNode::updateNode(
{ {
d->hash = hash; d->hash = hash;
if ( borderWidth > 0.0 && borderColor.isValid() ) QskArcRenderer::renderArc( rect, metrics, radial,
{ borderWidth, gradient, borderColor, *geometry() );
borderWidth = std::min( borderWidth, borderMax );
QskArcRenderer::renderBorder(
rect, metrics, radial, borderWidth, borderColor, *geometry() );
}
if ( hasFilling )
{
QskArcRenderer::renderFillGeometry(
rect, metrics, radial, borderWidth, gradient, *geometry() );
}
markDirty( QSGNode::DirtyGeometry ); markDirty( QSGNode::DirtyGeometry );
markDirty( QSGNode::DirtyMaterial ); markDirty( QSGNode::DirtyMaterial );

View File

@ -8,6 +8,7 @@
#include "QskGradient.h" #include "QskGradient.h"
#include "QskVertex.h" #include "QskVertex.h"
#include "QskBoxColorMap.h" #include "QskBoxColorMap.h"
#include "QskRgbValue.h"
#include <qsggeometry.h> #include <qsggeometry.h>
#include <qdebug.h> #include <qdebug.h>
@ -210,12 +211,12 @@ namespace
{ {
public: public:
ArcStroker( const QRectF&, const QskArcMetrics&, ArcStroker( const QRectF&, const QskArcMetrics&,
bool radial, const QskGradient&, const QColor& borderColor ); bool radial, const QskGradient&, const QskVertex::Color& );
int fillCount() const; int fillCount() const;
int setFillLines( qreal thickness, qreal border, QskVertex::ColoredLine* ) const;
int borderCount() const; int borderCount() const;
int setFillLines( qreal thickness, qreal border, QskVertex::ColoredLine* ) const;
int setBorderLines( qreal thickness, qreal border, QskVertex::ColoredLine* ) const; int setBorderLines( qreal thickness, qreal border, QskVertex::ColoredLine* ) const;
private: private:
@ -240,7 +241,7 @@ namespace
}; };
ArcStroker::ArcStroker( const QRectF& rect, const QskArcMetrics& metrics, ArcStroker::ArcStroker( const QRectF& rect, const QskArcMetrics& metrics,
bool radial, const QskGradient& gradient, const QColor& borderColor ) bool radial, const QskGradient& gradient, const QskVertex::Color& borderColor )
: m_rect( rect ) : m_rect( rect )
, m_radians1( qDegreesToRadians( metrics.startAngle() ) ) , m_radians1( qDegreesToRadians( metrics.startAngle() ) )
, m_radians2( qDegreesToRadians( metrics.endAngle() ) ) , m_radians2( qDegreesToRadians( metrics.endAngle() ) )
@ -264,6 +265,9 @@ namespace
int ArcStroker::fillCount() const int ArcStroker::fillCount() const
{ {
if ( !m_gradient.isVisible() )
return 0;
return arcLineCount() + m_gradient.stepCount() - 1; return arcLineCount() + m_gradient.stepCount() - 1;
} }
@ -325,6 +329,9 @@ namespace
int ArcStroker::borderCount() const int ArcStroker::borderCount() const
{ {
if ( m_borderColor.a == 0 )
return 0;
auto count = 2 * arcLineCount(); auto count = 2 * arcLineCount();
if ( !m_closed ) if ( !m_closed )
count += 2 * 3; count += 2 * 3;
@ -390,62 +397,74 @@ bool QskArcRenderer::isGradientSupported( const QskGradient& gradient )
return true; return true;
} }
void QskArcRenderer::renderFillGeometry( const QRectF& rect, void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics,
const QskArcMetrics& metrics, bool radial, qreal borderWidth, bool radial, const QskGradient& gradient, QSGGeometry& geometry )
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,
const QColor& borderColor, QSGGeometry& geometry )
{ {
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
ArcStroker stroker( rect, metrics, radial, gradient, QColor() ); ArcStroker stroker( rect, metrics, radial, gradient,
borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) );
const auto lineCount = stroker.fillCount(); const auto borderCount = stroker.borderCount();
const auto fillCount = stroker.fillCount();
auto lineCount = borderCount + fillCount;
if ( borderCount && fillCount )
lineCount++; // connecting line
const auto lines = qskAllocateColoredLines( geometry, lineCount ); const auto lines = qskAllocateColoredLines( geometry, lineCount );
if ( lines == nullptr ) if ( lines == nullptr )
return; return;
const auto effectiveCount = stroker.setFillLines( if ( fillCount )
metrics.thickness(), borderWidth, lines );
if ( effectiveCount > lineCount )
{ {
qFatal( "geometry: allocated memory exceeded: %d vs. %d", const auto effectiveCount = stroker.setFillLines(
effectiveCount, lineCount );
}
if ( effectiveCount < lineCount )
{
/*
Gradient or contour lines might be at the same position
and we end up with less lines, than expected.
*/
for ( int i = effectiveCount; i < lineCount; i++ )
lines[i] = lines[i - 1];
}
}
void QskArcRenderer::renderBorder( const QRectF& rect, const QskArcMetrics& metrics,
bool radial, qreal borderWidth, const QColor& borderColor, QSGGeometry& geometry )
{
Q_ASSERT( borderWidth > 0.0 );
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
ArcStroker stroker( rect, metrics, radial, QskGradient(), borderColor );
const auto lineCount = stroker.borderCount();
const auto lines = qskAllocateColoredLines( geometry, lineCount );
if ( lines )
{
const auto effectiveCount = stroker.setBorderLines(
metrics.thickness(), borderWidth, lines ); metrics.thickness(), borderWidth, lines );
if ( lineCount != effectiveCount ) if ( effectiveCount > fillCount )
{
qFatal( "geometry: allocated memory exceeded: %d vs. %d",
effectiveCount, fillCount );
}
if ( effectiveCount < fillCount )
{
/*
Gradient or contour lines might be at the same position
and we end up with less lines, than expected.
*/
for ( int i = effectiveCount; i < fillCount; i++ )
lines[i] = lines[i - 1];
}
}
if ( borderCount > 0 )
{
auto borderLines = lines + lineCount - borderCount;
const auto effectiveCount = stroker.setBorderLines(
metrics.thickness(), borderWidth, borderLines );
if ( borderCount != effectiveCount )
{ {
qFatal( "geometry: allocated memory does not match: %d vs. %d", qFatal( "geometry: allocated memory does not match: %d vs. %d",
effectiveCount, lineCount ); effectiveCount, borderCount );
}
if ( fillCount && borderCount )
{
const auto idx = fillCount;
lines[idx].p1 = lines[idx-1].p2;
lines[idx].p2 = lines[idx+1].p1;
} }
} }
} }

View File

@ -37,11 +37,11 @@ namespace QskArcRenderer
*/ */
QSK_EXPORT bool isGradientSupported( const QskGradient& ); QSK_EXPORT bool isGradientSupported( const QskGradient& );
QSK_EXPORT void renderBorder( const QRectF&, const QskArcMetrics&, QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial,
bool radial, qreal borderWidth, const QColor& borderColor, QSGGeometry& ); qreal borderWidth, const QskGradient&, const QColor& borderColor, QSGGeometry& );
QSK_EXPORT void renderFillGeometry( const QRectF&, const QskArcMetrics&, QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial,
bool radial, qreal borderWidth, const QskGradient&, QSGGeometry& ); const QskGradient&, QSGGeometry& );
} }
#endif #endif