add QskArcShadowNode infrastructure

This commit is contained in:
Vogel, Rick 2023-12-09 16:18:11 +01:00
parent 8d74b08c08
commit eb18c58830
8 changed files with 436 additions and 138 deletions

View File

@ -287,7 +287,7 @@ namespace
auto sliderStart = new SliderBox( "Angle", 0.0, 360.0, metrics.startAngle() ); auto sliderStart = new SliderBox( "Angle", 0.0, 360.0, metrics.startAngle() );
auto sliderSpan = new SliderBox( "Span", -360.0, 360.0, metrics.spanAngle() ); auto sliderSpan = new SliderBox( "Span", -360.0, 360.0, metrics.spanAngle() );
auto sliderExtent = new SliderBox( "Extent", 10.0, 100.0, metrics.thickness() ); auto sliderExtent = new SliderBox( "Extent", 10.0, 100.0, metrics.thickness() );
auto shadowExtent = new SliderBox( "Shadow Extent", 0.0, 100.0, 50 ); auto shadowExtent = new SliderBox( "Shadow Extent", 0.0, 1.0, 0.5 );
auto sliderOffsetX = new SliderBox( "Offset X", -1.0, +1.0, 0 ); auto sliderOffsetX = new SliderBox( "Offset X", -1.0, +1.0, 0 );
auto sliderOffsetY = new SliderBox( "Offset Y", -1.0, +1.0, 0 ); auto sliderOffsetY = new SliderBox( "Offset Y", -1.0, +1.0, 0 );
auto sliderStrokeWidth = new SliderBox( "Stroke Width", 0, 10, 1 ); auto sliderStrokeWidth = new SliderBox( "Stroke Width", 0, 10, 1 );
@ -421,6 +421,7 @@ ChartView::ChartView( ArcControl* chart, QQuickItem* parent )
// legend->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); // legend->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed );
// legend->setSamples( chart->series() ); // legend->setSamples( chart->series() );
hBox->addItem(chart); hBox->addItem(chart);
hBox->setDefaultAlignment(Qt::AlignCenter);
auto controlPanel = new ControlPanel( chart->arcMetricsHint(QskControl::Background) ); auto controlPanel = new ControlPanel( chart->arcMetricsHint(QskControl::Background) );
controlPanel->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); controlPanel->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed );

View File

@ -129,16 +129,16 @@ int main( int argc, char* argv[] )
metrics.setThickness(10); metrics.setThickness(10);
QskShadowMetrics shadowMetrics; QskShadowMetrics shadowMetrics;
shadowMetrics.setSpreadRadius(10); shadowMetrics.setSpreadRadius(0.1);
shadowMetrics.setSizeMode(Qt::SizeMode::RelativeSize); shadowMetrics.setSizeMode(Qt::SizeMode::RelativeSize);
control->setBackgroundColor(Qt::white);
control->setGradientHint(Q::Arc, {Qt::red}); control->setGradientHint(Q::Arc, {Qt::red});
control->setArcMetricsHint(Q::Arc, metrics); control->setArcMetricsHint(Q::Arc, metrics);
control->setMetric(Q::Arc | QskAspect::Border, 4); control->setMetric(Q::Arc | QskAspect::Border, 4);
control->setColor(Q::Arc | QskAspect::Border, Qt::blue); control->setColor(Q::Arc | QskAspect::Border, Qt::blue);
control->setShadowColorHint(Q::Arc, Qt::blue); control->setShadowColorHint(Q::Arc, Qt::black);
control->setShadowMetricsHint(Q::Arc, shadowMetrics); control->setShadowMetricsHint(Q::Arc, shadowMetrics);
control->setFixedSize( 300, 250 );
QskWindow window; QskWindow window;
window.addItem( new ChartView( control ) ); window.addItem( new ChartView( control ) );

View File

@ -99,6 +99,7 @@ list(APPEND SOURCES
list(APPEND HEADERS list(APPEND HEADERS
nodes/QskArcNode.h nodes/QskArcNode.h
nodes/QskArcShadowNode.h
nodes/QskBasicLinesNode.h nodes/QskBasicLinesNode.h
nodes/QskBoxNode.h nodes/QskBoxNode.h
nodes/QskBoxClipNode.h nodes/QskBoxClipNode.h
@ -136,6 +137,7 @@ list(APPEND PRIVATE_HEADERS
list(APPEND SOURCES list(APPEND SOURCES
nodes/QskArcNode.cpp nodes/QskArcNode.cpp
nodes/QskArcShadowNode.cpp
nodes/QskBasicLinesNode.cpp nodes/QskBasicLinesNode.cpp
nodes/QskBoxNode.cpp nodes/QskBoxNode.cpp
nodes/QskBoxClipNode.cpp nodes/QskBoxClipNode.cpp

View File

@ -5,6 +5,7 @@
#include "QskArcNode.h" #include "QskArcNode.h"
#include "QskArcMetrics.h" #include "QskArcMetrics.h"
#include "QskArcShadowNode.h"
#include "QskMargins.h" #include "QskMargins.h"
#include "QskGradient.h" #include "QskGradient.h"
#include "QskShapeNode.h" #include "QskShapeNode.h"
@ -20,105 +21,6 @@
#include <QDebug> #include <QDebug>
#include <qmath.h> #include <qmath.h>
namespace
{
struct QskArcShadowMaterialProperties
{
QColor color = Qt::red;
QRectF rect;
QPointF offset;
qreal radius = 1.0; // [0.0,1.0]
qreal thickness = 0.2;
qreal startAngle = 0.0; //< degree [0.0,360.0]
qreal spanAngle = 270.0; //< degree [0.0,360.0]
qreal extend = 16.0; //< pixel >= 0.0
};
class QskArcShadowMaterial final : public QSGSimpleMaterialShader<QskArcShadowMaterialProperties>
{
QSG_DECLARE_SIMPLE_SHADER(QskArcShadowMaterial, QskArcShadowMaterialProperties)
public:
QskArcShadowMaterial()
{
const QString root( ":/qskinny/shaders/" );
setShaderSourceFile( QOpenGLShader::Vertex, root + "arcshadow.vert" );
setShaderSourceFile( QOpenGLShader::Fragment, root + "arcshadow.frag" );
}
QList<QByteArray> attributes() const override {
return QList<QByteArray>() << "vertex";
}
void updateState( const QskArcShadowMaterialProperties* newState,
const QskArcShadowMaterialProperties* oldState ) override
{
std::ignore = oldState;
const auto& color = newState->color;
const auto& rect = newState->rect;
const auto& radius = newState->radius;
const auto& thickness = newState->thickness;
const auto& startAngle = newState->startAngle;
const auto& spanAngle = newState->spanAngle;
const auto& extend = newState->extend;
const auto& offset = newState->offset;
auto& p = *program();
p.setUniformValue( "color", color.redF(), color.greenF(), color.blueF(), 1.0f );
p.setUniformValue( "rect", rect.x(), rect.y(), rect.width(), rect.height() );
p.setUniformValue( "radius", ( float ) radius );
p.setUniformValue( "thickness", ( float ) thickness );
p.setUniformValue( "startAngle", ( float ) startAngle - 90.0f );
p.setUniformValue( "spanAngle", ( float ) spanAngle );
p.setUniformValue( "extend", ( float ) extend );
p.setUniformValue( "offset", ( float ) offset.x(), ( float ) offset.y() );
}
};
class QskArcShadowNode : public QSGGeometryNode
{
public:
QskArcShadowNode() : m_geometry(QSGGeometry::defaultAttributes_Point2D(), 4)
{
setGeometry(&m_geometry);
setMaterial(QskArcShadowMaterial::createMaterial());
m_geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
material()->setFlag(QSGMaterial::Blending);
}
void update(const QRectF& rect, const QskArcMetrics& metrics, const QColor& color, const QskShadowMetrics& shadowMetrics = {}, const qreal borderWidth = 0.0)
{
auto* const vertices = geometry()->vertexDataAsPoint2D();
const auto b = borderWidth / 2;
const auto r = rect.adjusted( -b, -b, +b, +b );
vertices[0].set(r.left(), r.top());
vertices[1].set(r.left(), r.bottom());
vertices[2].set(r.right(), r.top());
vertices[3].set(r.right(), r.bottom());
markDirty( QSGNode::DirtyGeometry );
const auto size = qMin(r.width(), r.height());
auto* const material = static_cast<QSGSimpleMaterial< QskArcShadowMaterialProperties >*>(this->material());
auto& state = *material->state();
state.color = color;
state.rect = r;
state.radius = 1.0 - (metrics.thickness() + borderWidth) / size;
state.thickness = 2 * metrics.thickness() / size;
state.startAngle = metrics.startAngle();
state.spanAngle = metrics.spanAngle();
state.extend = shadowMetrics.spreadRadius();
state.offset = shadowMetrics.offset();
markDirty( QSGNode::DirtyMaterial );
}
private:
QSGGeometry m_geometry;
};
}
static inline QskGradient qskEffectiveGradient( static inline QskGradient qskEffectiveGradient(
const QskGradient& gradient, const QskArcMetrics& metrics ) const QskGradient& gradient, const QskArcMetrics& metrics )
{ {

View File

@ -0,0 +1,371 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskArcShadowNode.h"
#include "QskArcMetrics.h"
#include "QskShadowMetrics.h"
#include <qcolor.h>
#include <qsgmaterial.h>
#include <qsgmaterialshader.h>
#include <qmath.h>
QSK_QT_PRIVATE_BEGIN
#include <private/qsgnode_p.h>
QSK_QT_PRIVATE_END
// QSGMaterialRhiShader became QSGMaterialShader in Qt6
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
#include <QSGMaterialRhiShader>
using RhiShader = QSGMaterialRhiShader;
#else
using RhiShader = QSGMaterialShader;
#endif
namespace
{
struct MaterialProperties
{
QVector4D color{ 0, 0, 0, 1 };
QVector2D offset;
float radius = 1.0f;
float thickness = 0.0f;
float startAngle = 0.0f;
float spanAngle = M_PI;
float extend = 0.0f;
Q_REQUIRED_RESULT inline bool operator==(const MaterialProperties& rhs) const noexcept
{
return color == rhs.color && offset == rhs.offset &&
radius == rhs.radius && thickness == rhs.thickness &&
startAngle == rhs.startAngle && spanAngle == rhs.spanAngle &&
extend == rhs.extend;
}
Q_REQUIRED_RESULT inline bool operator!=(const MaterialProperties& rhs) const noexcept
{
return !(*this == rhs);
}
};
class Material final : public QSGMaterial
{
public:
Material();
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QSGMaterialShader* createShader() const override;
#else
QSGMaterialShader* createShader( QSGRendererInterface::RenderMode ) const override;
#endif
QSGMaterialType* type() const override;
int compare( const QSGMaterial* other ) const override;
MaterialProperties properties;
};
}
namespace
{
class ShaderRhi final : public RhiShader
{
public:
ShaderRhi()
{
// TODO
}
bool updateUniformData( RenderState& state,
QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) override
{
// TODO
return false;
}
};
}
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
namespace
{
// the old type of shader - specific for OpenGL
class ShaderGL final : public QSGMaterialShader
{
struct Uniforms
{
int matrix = -1;
int opacity = -1;
int color = -1;
int offset = -1;
int radius = -1;
int thickness = -1;
int startAngle = -1;
int spanAngle = -1;
int extend = -1;
};
public:
ShaderGL()
{
const QString root( ":/qskinny/shaders/" );
setShaderSourceFile( QOpenGLShader::Vertex, root + "arcshadow.vert" );
setShaderSourceFile( QOpenGLShader::Fragment, root + "arcshadow.frag" );
}
char const* const* attributeNames() const override
{
static char const* const names[] = { "in_vertex", "in_coord", nullptr };
return names;
}
void initialize() override
{
QSGMaterialShader::initialize();
const auto* const p = program();
id.matrix = p->uniformLocation( "matrix" );
id.opacity = p->uniformLocation( "opacity" );
id.color = p->uniformLocation( "color" );
id.offset = p->uniformLocation( "offset" );
id.radius = p->uniformLocation( "radius" );
id.thickness = p->uniformLocation( "thickness" );
id.startAngle = p->uniformLocation( "startAngle" );
id.spanAngle = p->uniformLocation( "spanAngle" );
id.extend = p->uniformLocation( "extend" );
}
void updateState( const QSGMaterialShader::RenderState& state,
QSGMaterial* const newMaterial, QSGMaterial* const oldMaterial) override
{
auto* const p = program();
if ( state.isMatrixDirty() )
{
p->setUniformValue( id.matrix, state.combinedMatrix() );
}
if ( state.isOpacityDirty() )
{
p->setUniformValue( id.opacity, state.opacity() );
}
const auto updateMaterial = ( oldMaterial == nullptr ) ||
( newMaterial->compare( oldMaterial ) != 0 ) ||
( state.isCachedMaterialDataDirty() );
if ( updateMaterial )
{
const auto* const material = static_cast< const Material* >( newMaterial );
const auto& properties = material->properties;
p->setUniformValue( id.color, properties.color);
p->setUniformValue( id.offset, properties.offset);
p->setUniformValue( id.radius, properties.radius);
p->setUniformValue( id.thickness, properties.thickness);
p->setUniformValue( id.startAngle, properties.startAngle);
p->setUniformValue( id.spanAngle, properties.spanAngle);
p->setUniformValue( id.extend, properties.extend);
}
}
private:
Uniforms id;
};
}
#endif
namespace
{
Material::Material()
{
setFlag( QSGMaterial::Blending, true );
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
setFlag( QSGMaterial::SupportsRhiShader, true );
#endif
}
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QSGMaterialShader* Material::createShader() const
{
if ( !( flags() & QSGMaterial::RhiShaderWanted ) )
return new ShaderGL();
return new ShaderRhi();
}
#else
QSGMaterialShader* Material::createShader( QSGRendererInterface::RenderMode ) const
{
return new ShaderRhi();
}
#endif
QSGMaterialType* Material::type() const
{
static QSGMaterialType staticType;
return &staticType;
}
int Material::compare( const QSGMaterial* other ) const
{
const auto& lhs = *static_cast< const Material* >( this );
const auto& rhs = *static_cast< const Material* >( other );
return ( lhs.properties == rhs.properties ) ? 0 : QSGMaterial::compare( other );
}
}
class QskArcShadowNodePrivate final : public QSGGeometryNodePrivate
{
public:
QskArcShadowNodePrivate()
: geometry( QSGGeometry::defaultAttributes_TexturedPoint2D(), 4 )
{
}
QSGGeometry geometry;
Material material;
QRectF rect;
};
QskArcShadowNode::QskArcShadowNode() : QSGGeometryNode( *new QskArcShadowNodePrivate )
{
Q_D( QskArcShadowNode );
setGeometry( &d->geometry );
setMaterial( &d->material );
d->geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
d->material.setFlag( QSGMaterial::Blending );
}
QskArcShadowNode::~QskArcShadowNode() = default;
namespace
{
Q_REQUIRED_RESULT inline bool operator==(const QRectF& lhs, const QVector4D& rhs) noexcept
{
return lhs.x() == rhs[0] && lhs.y() == rhs[1] && lhs.width() == rhs[2] && lhs.height() == rhs[3];
}
Q_REQUIRED_RESULT inline bool operator!=(const QRectF& lhs, const QVector4D& rhs) noexcept
{
return !(lhs == rhs);
}
Q_REQUIRED_RESULT inline bool compareExchange(float& dst, const float src)
{
if ( dst != src )
{
return true;
}
return false;
}
}
void QskArcShadowNode::update( const QRectF& rect, const QskArcMetrics& arcMetrics,
const QColor& shadowColor, const QskShadowMetrics& shadowMetrics, const qreal borderWidth )
{
Q_D( QskArcShadowNode );
const auto w = qMax(0.0, borderWidth);
const auto shadowRect = rect.adjusted(-w, -w, +w, +w);
const auto size = qMin( shadowRect.width(), shadowRect.height() );
const auto color = [](const QColor& color)
{
return QVector4D
{
qBound(0.0f, ( float ) color.redF(), 1.0f),
qBound(0.0f, ( float ) color.greenF(), 1.0f),
qBound(0.0f, ( float ) color.blueF(), 1.0f),
qBound(0.0f, ( float ) color.alphaF(), 1.0f)
};
}
(shadowColor);
const auto offset = [](const QskShadowMetrics& metrics, const QRectF& rect)
{
QVector2D offset;
offset[ 0 ] = ( float ) metrics.offset().x();
offset[ 1 ] = ( float ) metrics.offset().y();
if ( metrics.sizeMode() == Qt::AbsoluteSize )
{
offset[ 0 ] = ( float ) (metrics.offset().x() / rect.width());
offset[ 1 ] = ( float ) (metrics.offset().y() / rect.height());
}
offset[ 0 ] = qBound( -1.0f, offset[ 0 ], +1.0f );
offset[ 1 ] = qBound( -1.0f, offset[ 1 ], +1.0f );
offset[ 0 ] *= -1.0f; // why must I change directions here?
offset[ 1 ] *= -1.0f; // why must I change directions here?
return offset;
}
(shadowMetrics, shadowRect);
const auto thickness = [](const QskArcMetrics& metrics, const QRectF& rect)
{
auto thickness = metrics.thickness();
const auto size = qMin( rect.width(), rect.height() );
if ( metrics.sizeMode() == Qt::AbsoluteSize )
{
thickness = thickness / size;
}
thickness = qBound( 0.0, thickness, 1.0 );
return ( float ) thickness;
}
(arcMetrics, shadowRect);
const auto spreadRadius = [](const QskShadowMetrics& metrics, const QRectF& rect)
{
auto spreadRadius = metrics.spreadRadius();
const auto size = qMin( rect.width(), rect.height() );
if ( metrics.sizeMode() == Qt::AbsoluteSize )
{
spreadRadius = spreadRadius / size;
}
spreadRadius = qBound( 0.0, spreadRadius, 1.0 );
return (float) spreadRadius;
}
(shadowMetrics, shadowRect);
const auto startAngle = (float) qDegreesToRadians(arcMetrics.startAngle() + 90.0); // why +90 ?
const auto spanAngle = (float) qDegreesToRadians(arcMetrics.spanAngle());
const auto radius = ( float ) ( 1.0 - thickness - 2 * w / size );
const MaterialProperties properties {
color,
offset,
radius,
thickness,
startAngle,
spanAngle,
spreadRadius
};
const auto dirtyGeometry = ( d->rect != shadowRect );
const auto dirtyMaterial = ( properties != d->material.properties );
if ( dirtyGeometry )
{
d->rect = shadowRect;
QSGGeometry::updateTexturedRectGeometry( &d->geometry, d->rect, { -0.5, -0.5, 1.0, 1.0 } );
d->geometry.markVertexDataDirty();
markDirty( QSGNode::DirtyGeometry );
}
if ( dirtyMaterial )
{
d->material.properties = properties;
markDirty( QSGNode::DirtyMaterial );
}
}

View File

@ -0,0 +1,30 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_ARC_SHADOW_NODE_H
#define QSK_ARC_SHADOW_NODE_H
#include "QskGlobal.h"
#include <qsgnode.h>
class QskArcMetrics;
class QskShadowMetrics;
class QskArcShadowNodePrivate;
class QskArcShadowNode : public QSGGeometryNode
{
public:
QskArcShadowNode();
~QskArcShadowNode() override;
void update( const QRectF& rect, const QskArcMetrics& metrics, const QColor& color,
const QskShadowMetrics& shadowMetrics, qreal borderWidth = 0.0 );
private:
Q_DECLARE_PRIVATE( QskArcShadowNode )
};
#endif

View File

@ -1,14 +1,15 @@
uniform lowp float qt_Opacity; uniform lowp float opacity;
// arc // arc
uniform lowp vec4 rect; // arc's rectangle on screen in pixel x,y,w,h
uniform lowp float radius; // arc's radius [0.0, 1.0] uniform lowp float radius; // arc's radius [0.0, 1.0]
uniform lowp float thickness; // arc's thickness uniform lowp float thickness; // arc's thickness [0.0, 1.0]
uniform lowp float startAngle; // arc's start angle uniform lowp float startAngle; // arc's start angle [rad]
uniform lowp float spanAngle; // arc's span angle uniform lowp float spanAngle; // arc's span angle [rad]
// shadow // shadow
uniform lowp vec4 color; // shadow's color uniform lowp vec4 color; // shadow's color
uniform lowp vec2 offset; // shadow's offset (x,y) : [-1.0, +1.0]x[-1.0,+1.0] uniform lowp vec2 offset; // shadow's offset (x,y) : [-1.0, +1.0]x[-1.0,+1.0]
uniform lowp float extend; // shadow length around uniform lowp float extend; // shadow length [0.0, 1.0]
// position
varying lowp vec2 coord; // [-1.0,+1.0]x[-1.0,+1.0]
float sdRing( in vec2 p, in vec2 n, in float r, in float th ) float sdRing( in vec2 p, in vec2 n, in float r, in float th )
{ {
@ -22,22 +23,10 @@ float sdRing( in vec2 p, in vec2 n, in float r, in float th )
void main() void main()
{ {
// uniforms
float shadowSize = extend; // px >= 0.0
vec2 iResolution = rect.zw;
// normalized pixel coordinates
vec2 p = (2.0 * gl_FragCoord.xy - iResolution.xy) / iResolution.xy; // xy for ellipse
float shadowSizeUv = shadowSize / min(iResolution.x, iResolution.y);
float t = radians(abs(spanAngle) / 2.0);
vec2 cs = vec2(cos(t),sin(t));
// rotation // rotation
float ra = radians(startAngle + spanAngle / 2.0); vec2 p = coord + offset;
float ra = -startAngle - spanAngle / 2.0;
{ {
p = p + offset;
float sin_ra = sin(ra); float sin_ra = sin(ra);
float cos_ra = cos(ra); float cos_ra = cos(ra);
mat2 transform = mat2 mat2 transform = mat2
@ -49,12 +38,10 @@ void main()
p = transform * p; p = transform * p;
} }
// distance float t = abs(spanAngle) / 2.0; // half span angle
float d = sdRing(p, cs, radius, thickness); vec2 cs = vec2(cos(t),sin(t));
float e = 1.0 / shadowSizeUv; float d = sdRing(p, cs, radius / 2.0, thickness);
// coloring float a = 1.0 - smoothstep(0.0, extend, d);
float v = 1.0 - abs(d) * e; gl_FragColor = vec4(color.rgb, 1.0) * a * opacity;
float a = d >= 0.0 && abs(d) < e ? v : 1.0; // alpha
gl_FragColor = vec4(color.rgb, 1.0) * a * qt_Opacity;
} }

View File

@ -1,7 +1,12 @@
attribute highp vec4 vertex; uniform highp mat4 matrix;
uniform highp mat4 qt_Matrix;
attribute highp vec4 in_vertex;
attribute mediump vec2 in_coord;
varying mediump vec2 coord;
void main() void main()
{ {
gl_Position = qt_Matrix * vertex; coord = in_coord;
gl_Position = matrix * in_vertex;
} }