qskinny/src/nodes/QskBoxShadowNode.cpp

345 lines
9.3 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskBoxShadowNode.h"
#include "QskBoxShapeMetrics.h"
#include <qcolor.h>
#include <qsgmaterialshader.h>
#include <qsgmaterial.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
{
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;
QVector2D m_aspect = QVector2D{ 1, 1 };
QVector4D m_radius = QVector4D{ 0, 0, 0, 0 };
QVector4D m_color = QVector4D{ 0, 0, 0, 1 };
float m_blurExtent = 0.0;
};
}
namespace
{
class ShaderRhi final : public RhiShader
{
public:
ShaderRhi()
{
const QString root( ":/qskinny/shaders/" );
setShaderFileName( VertexStage, root + "boxshadow.vert.qsb" );
setShaderFileName( FragmentStage, root + "boxshadow.frag.qsb" );
}
bool updateUniformData( RenderState& state,
QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) override
{
const auto matOld = static_cast< Material* >( oldMaterial );
const auto matNew = static_cast< Material* >( newMaterial );
Q_ASSERT( state.uniformData()->size() >= 112 );
auto data = state.uniformData()->data();
bool changed = false;
if ( state.isMatrixDirty() )
{
const auto matrix = state.combinedMatrix();
memcpy( data + 0, matrix.constData(), 64 );
changed = true;
}
if ( matOld == nullptr || matNew->m_color != matOld->m_color )
{
memcpy( data + 64, &matNew->m_color, 16 );
changed = true;
}
if ( matOld == nullptr || matNew->m_radius != matOld->m_radius )
{
memcpy( data + 80, &matNew->m_radius, 16 );
changed = true;
}
if ( matOld == nullptr || matNew->m_aspect != matOld->m_aspect )
{
memcpy( data + 96, &matNew->m_aspect, 8 );
changed = true;
}
if ( matOld == nullptr || matNew->m_blurExtent != matOld->m_blurExtent )
{
memcpy( data + 104, &matNew->m_blurExtent, 4 );
changed = true;
}
if ( state.isOpacityDirty() )
{
const float opacity = state.opacity();
memcpy( data + 108, &opacity, 4 );
changed = true;
}
return changed;
}
};
}
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
namespace
{
// the old type of shader - spcific for OpenGL
class ShaderGL final : public QSGMaterialShader
{
public:
ShaderGL()
{
const QString root( ":/qskinny/shaders/" );
setShaderSourceFile( QOpenGLShader::Vertex, root + "boxshadow.vert" );
setShaderSourceFile( QOpenGLShader::Fragment, root + "boxshadow.frag" );
}
char const* const* attributeNames() const override
{
static char const* const names[] = { "in_vertex", "in_coord", nullptr };
return names;
}
void initialize() override
{
QSGMaterialShader::initialize();
auto p = program();
m_matrixId = p->uniformLocation( "matrix" );
m_aspectId = p->uniformLocation( "aspect" );
m_opacityId = p->uniformLocation( "opacity" );
m_blurExtentId = p->uniformLocation( "blurExtent" );
m_radiusId = p->uniformLocation( "radius" );
m_colorId = p->uniformLocation( "color" );
}
void updateState( const QSGMaterialShader::RenderState& state,
QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override
{
auto p = program();
if ( state.isMatrixDirty() )
p->setUniformValue( m_matrixId, state.combinedMatrix() );
if ( state.isOpacityDirty() )
p->setUniformValue( m_opacityId, state.opacity() );
bool updateMaterial = ( oldMaterial == nullptr )
|| newMaterial->compare( oldMaterial ) != 0;
updateMaterial |= state.isCachedMaterialDataDirty();
if ( updateMaterial )
{
auto material = static_cast< const Material* >( newMaterial );
p->setUniformValue( m_aspectId, material->m_aspect );
p->setUniformValue( m_blurExtentId, material->m_blurExtent);
p->setUniformValue( m_radiusId, material->m_radius );
p->setUniformValue( m_colorId, material->m_color );
}
}
private:
int m_matrixId = -1;
int m_opacityId = -1;
int m_aspectId = -1;
int m_blurExtentId = -1;
int m_radiusId = -1;
int m_colorId = -1;
};
}
#endif
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
{
auto material = static_cast< const Material* >( other );
if ( material->m_color != m_color
|| material->m_aspect != m_aspect
|| !qFuzzyCompare(material->m_blurExtent, m_blurExtent)
|| !qFuzzyCompare(material->m_radius, m_radius) )
{
return 1;
}
return QSGMaterial::compare( other );
}
class QskBoxShadowNodePrivate final : public QSGGeometryNodePrivate
{
public:
QskBoxShadowNodePrivate()
: geometry( QSGGeometry::defaultAttributes_TexturedPoint2D(), 4 )
{
}
QSGGeometry geometry;
Material material;
QRectF rect;
};
QskBoxShadowNode::QskBoxShadowNode()
: QSGGeometryNode( *new QskBoxShadowNodePrivate )
{
Q_D( QskBoxShadowNode );
setGeometry( &d->geometry );
setMaterial( &d->material );
}
QskBoxShadowNode::~QskBoxShadowNode()
{
}
void QskBoxShadowNode::setShadowData(
const QRectF& rect, const QskBoxShapeMetrics& shape,
qreal blurRadius, const QColor& color )
{
Q_D( QskBoxShadowNode );
if ( rect != d->rect )
{
d->rect = rect;
QSGGeometry::updateTexturedRectGeometry(
&d->geometry, d->rect, QRectF( -0.5, -0.5, 1.0, 1.0 ) );
markDirty( QSGNode::DirtyGeometry );
QVector2D aspect( 1.0, 1.0 );
if ( rect.width() >= rect.height() )
aspect.setX( rect.width() / rect.height() );
else
aspect.setY( rect.height() / rect.width() );
if ( d->material.m_aspect != aspect )
{
d->material.m_aspect = aspect;
markDirty( QSGNode::DirtyMaterial );
}
}
{
const float t = std::min( d->rect.width(), d->rect.height() );
const float r1 = shape.radius( Qt::BottomRightCorner ).width();
const float r2 = shape.radius( Qt::TopRightCorner ).width();
const float r3 = shape.radius( Qt::BottomLeftCorner ).width();
const float r4 = shape.radius( Qt::TopLeftCorner ).width();
const auto uniformRadius = QVector4D(
std::min( r1 / t, 1.0f ), std::min( r2 / t, 1.0f ),
std::min( r3 / t, 1.0f ), std::min( r4 / t, 1.0f ) );
if ( d->material.m_radius != uniformRadius )
{
d->material.m_radius = uniformRadius;
markDirty( QSGNode::DirtyMaterial );
}
}
{
if ( blurRadius <= 0.0 )
blurRadius = 0.0;
const float t = 0.5 * std::min( d->rect.width(), d->rect.height() );
const float uniformExtent = blurRadius / t;
if ( !qFuzzyCompare( d->material.m_blurExtent, uniformExtent ) )
{
d->material.m_blurExtent = uniformExtent;
markDirty( QSGNode::DirtyMaterial );
}
}
{
const auto a = color.alphaF();
const QVector4D c( color.redF() * a, color.greenF() * a, color.blueF() * a, a );
if ( d->material.m_color != c )
{
d->material.m_color = c;
markDirty( QSGNode::DirtyMaterial );
}
}
}