From eb18c588308659a6e464f211635486b20ff6ebb3 Mon Sep 17 00:00:00 2001 From: "Vogel, Rick" Date: Sat, 9 Dec 2023 16:18:11 +0100 Subject: [PATCH] add QskArcShadowNode infrastructure --- playground/charts/ChartView.cpp | 3 +- playground/charts/main.cpp | 6 +- src/CMakeLists.txt | 2 + src/nodes/QskArcNode.cpp | 100 +-------- src/nodes/QskArcShadowNode.cpp | 371 +++++++++++++++++++++++++++++++ src/nodes/QskArcShadowNode.h | 30 +++ src/nodes/shaders/arcshadow.frag | 49 ++-- src/nodes/shaders/arcshadow.vert | 13 +- 8 files changed, 436 insertions(+), 138 deletions(-) create mode 100644 src/nodes/QskArcShadowNode.cpp create mode 100644 src/nodes/QskArcShadowNode.h diff --git a/playground/charts/ChartView.cpp b/playground/charts/ChartView.cpp index 2657663f..9b245501 100644 --- a/playground/charts/ChartView.cpp +++ b/playground/charts/ChartView.cpp @@ -287,7 +287,7 @@ namespace auto sliderStart = new SliderBox( "Angle", 0.0, 360.0, metrics.startAngle() ); auto sliderSpan = new SliderBox( "Span", -360.0, 360.0, metrics.spanAngle() ); 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 sliderOffsetY = new SliderBox( "Offset Y", -1.0, +1.0, 0 ); 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->setSamples( chart->series() ); hBox->addItem(chart); + hBox->setDefaultAlignment(Qt::AlignCenter); auto controlPanel = new ControlPanel( chart->arcMetricsHint(QskControl::Background) ); controlPanel->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); diff --git a/playground/charts/main.cpp b/playground/charts/main.cpp index 5cd1fc54..ae9a53f8 100644 --- a/playground/charts/main.cpp +++ b/playground/charts/main.cpp @@ -129,16 +129,16 @@ int main( int argc, char* argv[] ) metrics.setThickness(10); QskShadowMetrics shadowMetrics; - shadowMetrics.setSpreadRadius(10); + shadowMetrics.setSpreadRadius(0.1); shadowMetrics.setSizeMode(Qt::SizeMode::RelativeSize); - control->setBackgroundColor(Qt::white); control->setGradientHint(Q::Arc, {Qt::red}); control->setArcMetricsHint(Q::Arc, metrics); control->setMetric(Q::Arc | QskAspect::Border, 4); 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->setFixedSize( 300, 250 ); QskWindow window; window.addItem( new ChartView( control ) ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 59ba35fa..0c2fb9aa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -99,6 +99,7 @@ list(APPEND SOURCES list(APPEND HEADERS nodes/QskArcNode.h + nodes/QskArcShadowNode.h nodes/QskBasicLinesNode.h nodes/QskBoxNode.h nodes/QskBoxClipNode.h @@ -136,6 +137,7 @@ list(APPEND PRIVATE_HEADERS list(APPEND SOURCES nodes/QskArcNode.cpp + nodes/QskArcShadowNode.cpp nodes/QskBasicLinesNode.cpp nodes/QskBoxNode.cpp nodes/QskBoxClipNode.cpp diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index 0da11554..7f1eea7a 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -5,6 +5,7 @@ #include "QskArcNode.h" #include "QskArcMetrics.h" +#include "QskArcShadowNode.h" #include "QskMargins.h" #include "QskGradient.h" #include "QskShapeNode.h" @@ -20,105 +21,6 @@ #include #include -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 - { - 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 attributes() const override { - return QList() << "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*>(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( const QskGradient& gradient, const QskArcMetrics& metrics ) { diff --git a/src/nodes/QskArcShadowNode.cpp b/src/nodes/QskArcShadowNode.cpp new file mode 100644 index 00000000..05a237d7 --- /dev/null +++ b/src/nodes/QskArcShadowNode.cpp @@ -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 +#include +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +// QSGMaterialRhiShader became QSGMaterialShader in Qt6 + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) +#include +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 ); + } +} \ No newline at end of file diff --git a/src/nodes/QskArcShadowNode.h b/src/nodes/QskArcShadowNode.h new file mode 100644 index 00000000..7747a7e5 --- /dev/null +++ b/src/nodes/QskArcShadowNode.h @@ -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 + +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 \ No newline at end of file diff --git a/src/nodes/shaders/arcshadow.frag b/src/nodes/shaders/arcshadow.frag index 2638a232..7e2bef1c 100644 --- a/src/nodes/shaders/arcshadow.frag +++ b/src/nodes/shaders/arcshadow.frag @@ -1,14 +1,15 @@ -uniform lowp float qt_Opacity; +uniform lowp float opacity; // 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 thickness; // arc's thickness -uniform lowp float startAngle; // arc's start angle -uniform lowp float spanAngle; // arc's span angle +uniform lowp float thickness; // arc's thickness [0.0, 1.0] +uniform lowp float startAngle; // arc's start angle [rad] +uniform lowp float spanAngle; // arc's span angle [rad] // shadow 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 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 ) { @@ -21,23 +22,11 @@ float sdRing( in vec2 p, in vec2 n, in float r, in float th ) } 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 - 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 cos_ra = cos(ra); mat2 transform = mat2 @@ -45,16 +34,14 @@ void main() cos_ra, -sin_ra, sin_ra, cos_ra ); - + p = transform * p; } - - // distance - float d = sdRing(p, cs, radius, thickness); - float e = 1.0 / shadowSizeUv; - - // coloring - float v = 1.0 - abs(d) * e; - float a = d >= 0.0 && abs(d) < e ? v : 1.0; // alpha - gl_FragColor = vec4(color.rgb, 1.0) * a * qt_Opacity; + + float t = abs(spanAngle) / 2.0; // half span angle + vec2 cs = vec2(cos(t),sin(t)); + float d = sdRing(p, cs, radius / 2.0, thickness); + + float a = 1.0 - smoothstep(0.0, extend, d); + gl_FragColor = vec4(color.rgb, 1.0) * a * opacity; } \ No newline at end of file diff --git a/src/nodes/shaders/arcshadow.vert b/src/nodes/shaders/arcshadow.vert index 9a5507ce..01850039 100644 --- a/src/nodes/shaders/arcshadow.vert +++ b/src/nodes/shaders/arcshadow.vert @@ -1,7 +1,12 @@ -attribute highp vec4 vertex; -uniform highp mat4 qt_Matrix; +uniform highp mat4 matrix; + +attribute highp vec4 in_vertex; +attribute mediump vec2 in_coord; + +varying mediump vec2 coord; void main() { - gl_Position = qt_Matrix * vertex; -} \ No newline at end of file + coord = in_coord; + gl_Position = matrix * in_vertex; +}