Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qsgcurvestrokenode_p.h"
#include "qsgcurvestrokenode_p_p.h"
QT_BEGIN_NAMESPACE
/*!
\class QSGCurveStrokeNode
\inmodule QtQuick
\internal
*/
QSGCurveStrokeNode::QSGCurveStrokeNode()
{
setFlag(OwnsGeometry, true);
qsgnode_set_description(this, QLatin1StringView("curve stroke"));
setGeometry(new QSGGeometry(attributes(), 0, 0));
// defer updateMaterial() until later
}
void QSGCurveStrokeNode::QSGCurveStrokeNode::updateMaterial()
{
const bool expandingInVertexShader = m_cosmetic || expandingStrokeEnabled();
m_material.reset(new QSGCurveStrokeMaterial(this, expandingInVertexShader));
setMaterial(m_material.data());
}
// Take the start, control and end point of a curve and return the points A, B, C
// representing the curve as Q(s) = A*s*s + B*s + C
std::array<QVector2D, 3> QSGCurveStrokeNode::curveABC(const std::array<QVector2D, 3> &p)
{
QVector2D a = p[0] - 2*p[1] + p[2];
QVector2D b = 2*p[1] - 2*p[0];
QVector2D c = p[0];
return {a, b, c};
}
/*!
Append a triangle with \a vtx corners within which the fragment shader will
draw the visible part of a quadratic curve from ctl[0] to ctl[2] with
control point ctl[1] (AKA a quadratic Bézier curve with 3 control points).
The \a normal vectors are used in the vertex shader to expand the triangle
according to its stroke width: therefore, it's ok for the triangle to be
degenerate, and get expanded to size in the vertex shader. Normals are
usually unit vectors, but it's also ok for some to have larger magnitudes,
to handle the case when miter corners need to be extended proportionally
farther as stroke width increases.
*/
void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &vtx,
const std::array<QVector2D, 3> &ctl,
const std::array<QVector2D, 3> &normal,
const std::array<float, 3> &extrusions)
{
auto abc = curveABC(ctl);
int currentVertex = m_uncookedVertexes.count();
for (int i = 0; i < 3; ++i) {
m_uncookedVertexes.append( { vtx[i].x(), vtx[i].y(),
abc[0].x(), abc[0].y(), abc[1].x(), abc[1].y(), abc[2].x(), abc[2].y(),
normal[i].x(), normal[i].y(), extrusions[i] } );
}
m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
}
/*!
Append a triangle with \a vtx corners within which the fragment shader will
draw the visible part of a line from ctl[0] to ctl[2].
The \a normal vectors are used in the vertex shader to expand the triangle
according to its stroke width: therefore, it's ok for the triangle to be
degenerate, and get expanded to size in the vertex shader. Normals are
usually unit vectors, but it's also ok for some to have larger magnitudes,
to handle the case when miter corners need to be extended proportionally
farther as stroke width increases.
*/
void QSGCurveStrokeNode::appendTriangle(const std::array<QVector2D, 3> &vtx,
const std::array<QVector2D, 2> &ctl,
const std::array<QVector2D, 3> &normal,
const std::array<float, 3> &extrusions)
{
// We could reduce this to a linear equation by setting A to (0,0).
// However, then we cannot use the cubic solution and need an additional
// code path in the shader. The following formulation looks more complicated
// but allows to always use the cubic solution.
auto A = ctl[1] - ctl[0];
auto B = QVector2D(0., 0.);
auto C = ctl[0];
int currentVertex = m_uncookedVertexes.count();
for (int i = 0; i < 3; ++i) {
m_uncookedVertexes.append( { vtx[i].x(), vtx[i].y(),
A.x(), A.y(), B.x(), B.y(), C.x(), C.y(),
normal[i].x(), normal[i].y(), extrusions[i] } );
}
m_uncookedIndexes << currentVertex << currentVertex + 1 << currentVertex + 2;
}
void QSGCurveStrokeNode::cookGeometry()
{
updateMaterial(); // by now, setCosmeticStroke has been called if necessary
QSGGeometry *g = geometry();
if (g->indexType() != QSGGeometry::UnsignedIntType) {
g = new QSGGeometry(attributes(),
m_uncookedVertexes.size(),
m_uncookedIndexes.size(),
QSGGeometry::UnsignedIntType);
setGeometry(g);
} else {
g->allocate(m_uncookedVertexes.size(), m_uncookedIndexes.size());
}
g->setDrawingMode(QSGGeometry::DrawTriangles);
memcpy(g->vertexData(),
m_uncookedVertexes.constData(),
g->vertexCount() * g->sizeOfVertex());
memcpy(g->indexData(),
m_uncookedIndexes.constData(),
g->indexCount() * g->sizeOfIndex());
m_uncookedIndexes.clear();
m_uncookedIndexes.squeeze();
m_uncookedVertexes.clear();
m_uncookedVertexes.squeeze();
}
const QSGGeometry::AttributeSet &QSGCurveStrokeNode::attributes()
{
static QSGGeometry::Attribute data[] = {
QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute), //vertexCoord
QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //A
QSGGeometry::Attribute::createWithAttributeType(2, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //B
QSGGeometry::Attribute::createWithAttributeType(3, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //C
QSGGeometry::Attribute::createWithAttributeType(4, 3, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), //normalExt
};
static QSGGeometry::AttributeSet attrs = { 5, sizeof(StrokeVertex), data };
return attrs;
}
// TODO remove when we consider the expanding-stroke shader to be stable for full-time use
bool QSGCurveStrokeNode::expandingStrokeEnabled()
{
static const bool ret = qEnvironmentVariableIntValue("QT_QUICKSHAPES_STROKE_EXPANDING");
return ret;
}
QT_END_NAMESPACE