QskArc rendering improved

This commit is contained in:
Uwe Rathmann 2024-09-23 14:56:39 +02:00
parent b17cabaa83
commit feabc1fd50
5 changed files with 290 additions and 174 deletions

View File

@ -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,28 +79,24 @@ 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 );
return;
}
const bool radial = false;
const auto metricsArc = arcMetrics.toAbsolute( rect.size() );
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 )
if ( hasShadow && hasFilling )
{
/*
The shader of the shadow node is for circular arcs and we have some
@ -112,6 +105,8 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
and not only to its radius. TODO ...
*/
auto 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();
@ -120,22 +115,32 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor );
}
auto pathNode = qskInsertOrRemoveNode< QskShapeNode >( this, PathRole,
hasFilling && !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) );
if ( pathNode )
if ( hasBorder || hasFilling )
{
const auto path = metricsArc.painterPath( rect, radial );
pathNode->updateNode( path, QTransform(), rect,
qskEffectiveGradient( gradient, metricsArc ) );
arcNode = qskNode< QskArcRenderNode >( this, ArcRole );
if ( hasBorder && hasFilling )
{
if ( !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) )
fillNode = qskNode< QskArcRenderNode >( this, FillRole );
}
auto arcNode = qskInsertOrRemoveNode< QskArcRenderNode >(
this, ArcRole, hasBorder || ( hasFilling && !pathNode ) );
if ( arcNode )
if ( fillNode )
{
arcNode->updateArc( rect, metricsArc, radial,
borderWidth, borderColor, pathNode ? QskGradient() : gradient );
arcNode->updateBorder( rect, metricsArc,
radial, borderWidth, borderColor );
fillNode->updateFilling( rect, metricsArc, radial, borderWidth, gradient );
}
else
{
arcNode->updateArc( rect, metricsArc,
radial, borderWidth, borderColor, gradient );
}
}
}
qskUpdateChildren( this, ShadowRole, shadowNode );
qskUpdateChildren( this, ArcRole, arcNode );
qskUpdateChildren( this, FillRole, fillNode );
}

View File

@ -11,41 +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 resetNode( QskArcRenderNode* node )
{
hash = 0;
m_metricsHash = m_colorsHash = 0;
node->resetGeometry();
}
inline bool updateHash( const QRectF& rect, const QskArcMetrics& metrics,
bool radial, qreal borderWidth, const QColor& borderColor,
const QskGradient& gradient )
inline bool updateMetrics( const QRectF& rect,
const QskArcMetrics& metrics, bool radial, qreal borderWidth )
{
QskHashValue value = 3496;
QskHashValue hash = 13000;
value = qHashBits( &rect, sizeof( QRectF ), value );
value = metrics.hash( value );
value = qHash( radial, value );
value = qHash( borderWidth, value );
value = qHashBits( &borderColor, sizeof( borderColor ), value );
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() )
value = gradient.hash( value );
hash = gradient.hash( hash );
if ( hash != value )
return updateHash( m_colorsHash, hash );
}
private:
inline bool updateHash( QskHashValue& value, const QskHashValue newValue ) const
{
hash = value;
if ( newValue != value )
{
value = newValue;
return true;
}
return false;
}
private:
QskHashValue hash = 0;
public:
QskHashValue m_metricsHash = 0;
QskHashValue m_colorsHash = 0;
};
QskArcRenderNode::QskArcRenderNode()
@ -69,47 +89,103 @@ void QskArcRenderNode::updateFilling( const QRectF& rect,
{
Q_D( QskArcRenderNode );
if ( rect.isEmpty() || arcMetrics.isNull() || !gradient.isVisible() )
{
d->resetNode( this );
return;
}
const auto metrics = arcMetrics.toAbsolute( rect.size() );
const auto borderMax = 0.5 * metrics.thickness();
const bool hasFill = gradient.isVisible() && ( borderWidth < borderMax );
const bool hasBorder = false;
if ( rect.isEmpty() || metrics.isNull() || !( hasFill || hasBorder ) )
if ( borderWidth >= 0.5 * metrics.thickness() )
{
d->resetNode( this );
return;
}
borderWidth = qMin( borderWidth, borderMax );
if ( !d->updateHash( rect, metrics, radial, borderWidth, QColor(), gradient ) )
{
d->resetNode( this );
return;
}
#if 1
const bool coloredGeometry = hasHint( PreferColoredGeometry )
&& QskArcRenderer::isGradientSupported( rect, metrics, gradient );
Q_ASSERT( coloredGeometry ); // TODO ...
#endif
auto& geometry = *this->geometry();
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 );
borderWidth, gradient, *geometry() );
markDirty( QSGNode::DirtyGeometry );
}
else
{
setColoring( rect, gradient );
if ( dirtyGeometry )
{
QskArcRenderer::setFillLines( rect, metrics,
radial, borderWidth, *geometry() );
markDirty( QSGNode::DirtyGeometry );
}
}
}
}
void QskArcRenderNode::updateBorder( const QRectF& rect,
const QskArcMetrics& metrics, bool radial,
const QskArcMetrics& arcMetrics, bool radial,
qreal borderWidth, const QColor& borderColor )
{
updateArc( rect, metrics, radial, borderWidth, borderColor, QskGradient() );
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(
@ -119,38 +195,50 @@ void QskArcRenderNode::updateArc(
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->resetNode( this );
return;
}
/*
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 );
if ( !d->updateHash( rect, metrics, radial, borderWidth, borderColor, gradient ) )
const bool isDirty = d->updateMetrics( rect, arcMetrics, radial, borderWidth )
|| d->updateColors( borderColor, gradient ) || !isGeometryColored();
if ( isDirty )
{
d->resetNode( this );
return;
}
#if 1
const bool coloredGeometry = hasHint( PreferColoredGeometry )
&& QskArcRenderer::isGradientSupported( rect, metrics, gradient );
Q_ASSERT( coloredGeometry ); // TODO ...
#endif
auto& geometry = *this->geometry();
setColoring( QskFillNode::Polychrome );
QskArcRenderer::setColoredBorderAndFillLines( rect, metrics, radial,
borderWidth, borderColor, gradient, geometry );
borderWidth, borderColor, gradient, *geometry() );
markDirty( QSGNode::DirtyGeometry );
}
}
else if ( hasBorder )
{
updateBorder( rect, arcMetrics, radial, borderWidth, borderColor );
}
else if ( hasFill )
{
updateFilling( rect, arcMetrics, radial, borderWidth, gradient );
}
else
{
d->resetNode( this );
return;
}
}

View File

@ -67,6 +67,8 @@ void QskBoxNode::updateNode( const QRectF& rect,
QskBoxRectangleNode* rectNode = nullptr;
QskBoxRectangleNode* fillNode = nullptr;
if ( !rect.isEmpty() )
{
if ( !shadowMetrics.isNull()
&& shadowColor.isValid() && shadowColor.alpha() != 0 )
{
@ -94,6 +96,7 @@ void QskBoxNode::updateNode( const QRectF& rect,
fillNode->updateFilling( rect, shape, borderMetrics, gradient );
}
}
}
qskUpdateChildren( this, ShadowRole, shadowNode );
qskUpdateChildren( this, BoxRole, rectNode );

View File

@ -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:
@ -30,7 +36,7 @@ class QskBoxRectangleNodePrivate final : public QskFillNodePrivate
hash = shape.hash( hash );
hash = borderMetrics.hash( hash );
return updateValue( m_metricsHash, hash );
return updateHash( m_metricsHash, hash );
}
inline bool updateColors(
@ -44,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 )
{
@ -105,7 +111,7 @@ void QskBoxRectangleNode::updateFilling( const QRectF& rect,
const bool dirtyMetrics = d->updateMetrics( rect, shape, borderMetrics );
const bool dirtyColors = d->updateColors( QskBoxBorderColors(), fillGradient )
&& ( coloredGeometry == isGeometryColored() );
|| ( coloredGeometry != isGeometryColored() );
if ( dirtyMetrics || dirtyColors )
{
@ -148,29 +154,43 @@ 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 coloredGeometry = hasHint( PreferColoredGeometry )
|| !borderColors.isMonochrome();
const bool dirtyMetrics = d->updateMetrics( rect, shape, borderMetrics );
const bool dirtyColors = d->updateColors( borderColors, QskGradient() );
const bool dirtyColors = d->updateColors( borderColors, QskGradient() )
|| ( coloredGeometry != isGeometryColored() );
if ( dirtyMetrics || dirtyColors )
{
const auto coloring = QskFillNode::Polychrome;
if ( coloredGeometry )
{
setColoring( QskFillNode::Polychrome );
if ( coloring == QskFillNode::Polychrome )
setColoring( coloring );
else
setColoring( borderColors.left().rgbStart() );
QskBoxRenderer::setColoredBorderLines( rect, shape, borderMetrics,
borderColors, *this->geometry() );
QskBoxRenderer::setColoredBorderLines( rect, shape,
borderMetrics, borderColors, *geometry() );
markDirty( QSGNode::DirtyGeometry );
}
else
{
setColoring( borderColors.left().rgbStart() );
if ( dirtyMetrics )
{
QskBoxRenderer::setBorderLines( rect, shape,
borderMetrics, *geometry() );
markDirty( QSGNode::DirtyGeometry );
}
}
}
}
void QskBoxRectangleNode::updateBox( const QRectF& rect,
@ -186,7 +206,7 @@ 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 )
{

View File

@ -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
};