8#include <QtCore/qthreadpool.h>
11#include <QtGui/qvector2d.h>
12#include <QtGui/qvector4d.h>
13#include <QtGui/private/qtriangulator_p.h>
14#include <QtGui/private/qtriangulatingstroker_p.h>
15#include <QtGui/private/qrhi_p.h>
17#include <QtQuick/private/qsgcurvefillnode_p.h>
18#include <QtQuick/private/qsgcurvestrokenode_p.h>
19#include <QtQuick/private/qquadpath_p.h>
20#include <QtQuick/private/qsgcurveprocessor_p.h>
21#include <QtQuick/qsgmaterial.h>
25Q_LOGGING_CATEGORY(lcShapeCurveRenderer,
"qt.shape.curverenderer");
30
31
32
33
34enum WireFrameType { SimpleWFT, StrokeWFT };
36class QQuickShapeWireFrameMaterialShader :
public QSGMaterialShader
39 QQuickShapeWireFrameMaterialShader(WireFrameType wft,
int viewCount) : m_wftype(wft)
41 setShaderFileName(VertexStage, wft == StrokeWFT ?
42 QStringLiteral(
":/qt-project.org/scenegraph/shaders_ng/shapestroke_wireframe.vert.qsb") :
43 QStringLiteral(
":/qt-project.org/shapes/shaders_ng/wireframe.vert.qsb"), viewCount);
44 setShaderFileName(FragmentStage,
46 QStringLiteral(
":/qt-project.org/scenegraph/shaders_ng/shapestroke_wireframe.frag.qsb") :
47 QStringLiteral(
":/qt-project.org/shapes/shaders_ng/wireframe.frag.qsb"), viewCount);
50 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
override;
52 WireFrameType m_wftype;
55class QQuickShapeWireFrameMaterial :
public QSGMaterial
58 QQuickShapeWireFrameMaterial(WireFrameType wft) : m_wftype(wft)
60 setFlag(Blending,
true);
63 int compare(
const QSGMaterial *other)
const override
65 return (type() - other->type());
68 void setCosmeticStroke(
bool c)
73 void setStrokeWidth(
float width)
75 m_strokeWidth = width;
80 return (m_cosmeticStroke ? -1.0 : 1.0) * qAbs(m_strokeWidth);
84 QSGMaterialType *type()
const override
86 static QSGMaterialType t;
89 QSGMaterialShader *createShader(QSGRendererInterface::RenderMode)
const override
91 return new QQuickShapeWireFrameMaterialShader(m_wftype, viewCount());
94 WireFrameType m_wftype;
95 bool m_cosmeticStroke =
false;
96 float m_strokeWidth = 1.0f;
99bool QQuickShapeWireFrameMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
101 QByteArray *buf = state.uniformData();
102 Q_ASSERT(buf->size() >= 64);
103 const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
104 bool changed =
false;
105 float localScale = 1.0f;
107 for (
int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
108 if (state.isMatrixDirty()) {
109 QMatrix4x4 m = state.combinedMatrix(viewIndex);
110 if (m_wftype == StrokeWFT)
112 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
117 const float matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio() * localScale;
118 memcpy(buf->data() + matrixCount * 64, &matrixScale, 4);
119 const float dpr = state.devicePixelRatio();
120 memcpy(buf->data() + matrixCount * 64 + 8, &dpr, 4);
121 const float opacity = 1.0;
122 memcpy(buf->data() + matrixCount * 64 + 4, &opacity, 4);
123 const float strokeWidth =
static_cast<QQuickShapeWireFrameMaterial *>(newMaterial)->strokeWidth();
124 memcpy(buf->data() + matrixCount * 64 + 12, &strokeWidth, 4);
131template <WireFrameType wftype>
132class QQuickShapeWireFrameNode :
public QSGCurveAbstractNode
135 struct WireFrameVertex
137 float x, y, u, v, w, nx, ny, sw;
140 QQuickShapeWireFrameNode()
143 setFlag(OwnsGeometry,
true);
144 setGeometry(
new QSGGeometry(attributes(), 0, 0));
148 void setColor(QColor col)
override
153 void setUseStandardDerivatives(
bool useStandardDerivatives) override
155 Q_UNUSED(useStandardDerivatives);
158 void setCosmeticStroke(
bool c)
160 m_material->setCosmeticStroke(c);
163 void setStrokeWidth(
float width)
165 m_material->setStrokeWidth(width);
168 void activateMaterial()
170 m_material.reset(
new QQuickShapeWireFrameMaterial(wftype));
171 setMaterial(m_material.data());
174 static const QSGGeometry::AttributeSet &attributes()
176 static QSGGeometry::Attribute data[] = {
177 QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
178 QSGGeometry::Attribute::createWithAttributeType(1, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
179 QSGGeometry::Attribute::createWithAttributeType(2, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
181 static QSGGeometry::AttributeSet attrs = { 3,
sizeof(WireFrameVertex), data };
185 void cookGeometry() override
191 QScopedPointer<QQuickShapeWireFrameMaterial> m_material;
195QQuickShapeCurveRenderer::~QQuickShapeCurveRenderer()
197 for (
const PathData &pd : std::as_const(m_paths)) {
198 if (pd.currentRunner) {
199 pd.currentRunner->orphaned =
true;
200 if (!pd.currentRunner->isAsync || pd.currentRunner->isDone)
201 delete pd.currentRunner;
206void QQuickShapeCurveRenderer::beginSync(
int totalCount,
bool *countChanged)
208 if (countChanged !=
nullptr && totalCount != m_paths.size())
209 *countChanged =
true;
210 for (
int i = totalCount; i < m_paths.size(); i++) {
211 setFillTextureProvider(i,
nullptr);
212 m_removedPaths.append(m_paths.at(i));
214 m_paths.resize(totalCount);
217void QQuickShapeCurveRenderer::setPath(
int index,
const QPainterPath &path, QQuickShapePath::PathHints pathHints)
219 auto &pathData = m_paths[index];
220 pathData.originalPath = path;
221 pathData.pathHints = pathHints;
222 pathData.m_dirty |= PathDirty;
225void QQuickShapeCurveRenderer::setStrokeColor(
int index,
const QColor &color)
227 auto &pathData = m_paths[index];
228 const bool wasVisible = pathData.isStrokeVisible();
229 pathData.pen.setColor(color);
230 if (pathData.isStrokeVisible() != wasVisible)
231 pathData.m_dirty |= StrokeDirty;
233 pathData.m_dirty |= UniformsDirty;
236void QQuickShapeCurveRenderer::setStrokeWidth(
int index, qreal w)
238 auto &pathData = m_paths[index];
240 pathData.validPenWidth =
true;
241 pathData.pen.setWidthF(w);
243 pathData.validPenWidth =
false;
245 pathData.m_dirty |= StrokeDirty;
248void QQuickShapeCurveRenderer::setCosmeticStroke(
int index,
bool c)
250 auto &pathData = m_paths[index];
251 pathData.pen.setCosmetic(c);
252 pathData.m_dirty |= StrokeDirty;
255void QQuickShapeCurveRenderer::setFillColor(
int index,
const QColor &color)
257 auto &pathData = m_paths[index];
258 const bool wasVisible = pathData.isFillVisible();
259 pathData.fillColor = color;
260 if (pathData.isFillVisible() != wasVisible)
261 pathData.m_dirty |= FillDirty;
263 pathData.m_dirty |= UniformsDirty;
266void QQuickShapeCurveRenderer::setFillRule(
int index, QQuickShapePath::FillRule fillRule)
268 auto &pathData = m_paths[index];
269 pathData.fillRule = Qt::FillRule(fillRule);
270 pathData.m_dirty |= PathDirty;
273void QQuickShapeCurveRenderer::setJoinStyle(
int index,
274 QQuickShapePath::JoinStyle joinStyle,
277 auto &pathData = m_paths[index];
278 pathData.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
279 pathData.pen.setMiterLimit(miterLimit);
280 pathData.m_dirty |= StrokeDirty;
283void QQuickShapeCurveRenderer::setCapStyle(
int index, QQuickShapePath::CapStyle capStyle)
285 auto &pathData = m_paths[index];
286 pathData.pen.setCapStyle(Qt::PenCapStyle(capStyle));
287 pathData.m_dirty |= StrokeDirty;
290void QQuickShapeCurveRenderer::setStrokeStyle(
int index,
291 QQuickShapePath::StrokeStyle strokeStyle,
293 const QList<qreal> &dashPattern)
295 auto &pathData = m_paths[index];
296 pathData.pen.setStyle(Qt::PenStyle(strokeStyle));
297 if (strokeStyle == QQuickShapePath::DashLine) {
298 pathData.pen.setDashPattern(dashPattern);
299 pathData.pen.setDashOffset(dashOffset);
301 pathData.m_dirty |= StrokeDirty;
304void QQuickShapeCurveRenderer::setFillGradient(
int index, QQuickShapeGradient *gradient)
306 PathData &pd(m_paths[index]);
307 const bool wasVisible = pd.isFillVisible();
308 pd.gradientType = QGradient::NoGradient;
309 if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) {
310 pd.gradientType = QGradient::LinearGradient;
311 pd.gradient.stops = gradient->gradientStops();
312 pd.gradient.spread = QGradient::Spread(gradient->spread());
313 pd.gradient.a = QPointF(g->x1(), g->y1());
314 pd.gradient.b = QPointF(g->x2(), g->y2());
315 }
else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(gradient)) {
316 pd.gradientType = QGradient::RadialGradient;
317 pd.gradient.a = QPointF(g->centerX(), g->centerY());
318 pd.gradient.b = QPointF(g->focalX(), g->focalY());
319 pd.gradient.v0 = g->centerRadius();
320 pd.gradient.v1 = g->focalRadius();
321 }
else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(gradient)) {
322 pd.gradientType = QGradient::ConicalGradient;
323 pd.gradient.a = QPointF(g->centerX(), g->centerY());
324 pd.gradient.v0 = g->angle();
325 }
else if (gradient !=
nullptr) {
326 static bool warned =
false;
329 qCWarning(lcShapeCurveRenderer) <<
"Unsupported gradient fill";
333 if (pd.gradientType != QGradient::NoGradient) {
334 pd.gradient.stops = gradient->gradientStops();
335 pd.gradient.spread = QGradient::Spread(gradient->spread());
338 pd.m_dirty |= (pd.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
341void QQuickShapeCurveRenderer::setFillTransform(
int index,
const QSGTransform &transform)
343 auto &pathData = m_paths[index];
344 pathData.fillTransform = transform;
345 pathData.m_dirty |= UniformsDirty;
348void QQuickShapeCurveRenderer::setFillTextureProvider(
int index, QQuickItem *textureProviderItem)
350 auto &pathData = m_paths[index];
351 const bool wasVisible = pathData.isFillVisible();
352 if (pathData.fillTextureProviderItem !=
nullptr)
353 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
354 pathData.fillTextureProviderItem = textureProviderItem;
355 if (pathData.fillTextureProviderItem !=
nullptr)
356 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(m_item->window());
357 pathData.m_dirty |= (pathData.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
360void QQuickShapeCurveRenderer::handleSceneChange(QQuickWindow *window)
362 for (
auto &pathData : m_paths) {
363 if (pathData.fillTextureProviderItem !=
nullptr) {
364 if (window ==
nullptr)
365 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
367 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(window);
372void QQuickShapeCurveRenderer::setAsyncCallback(
void (*callback)(
void *),
void *data)
374 m_asyncCallback = callback;
375 m_asyncCallbackData = data;
378void QQuickShapeCurveRenderer::endSync(
bool async)
380 bool asyncThreadsRunning =
false;
382 for (PathData &pathData : m_paths) {
383 if (!pathData.m_dirty)
386 if (pathData.m_dirty == UniformsDirty) {
391 if (pathData.currentRunner) {
394 if (pathData.currentRunner->isAsync) {
397 asyncThreadsRunning =
true;
401 delete pathData.currentRunner;
402 pathData.currentRunner =
nullptr;
406 pathData.currentRunner =
new QQuickShapeCurveRunnable;
407 setUpRunner(&pathData);
411 pathData.currentRunner->isAsync =
true;
412 QThreadPool::globalInstance()->start(pathData.currentRunner);
413 asyncThreadsRunning =
true;
417 pathData.currentRunner->run();
421 if (async && !asyncThreadsRunning && m_asyncCallback)
422 m_asyncCallback(m_asyncCallbackData);
425void QQuickShapeCurveRenderer::setUpRunner(PathData *pathData)
427 Q_ASSERT(pathData->currentRunner);
428 QQuickShapeCurveRunnable *runner = pathData->currentRunner;
429 runner->isDone =
false;
430 runner->pathData = *pathData;
431 runner->pathData.fillNodes.clear();
432 runner->pathData.strokeNodes.clear();
433 runner->pathData.currentRunner =
nullptr;
434 pathData->m_dirty = 0;
435 if (!runner->isInitialized) {
436 runner->isInitialized =
true;
437 runner->setAutoDelete(
false);
438 QObject::connect(runner, &QQuickShapeCurveRunnable::done, qApp,
439 [
this](QQuickShapeCurveRunnable *r) {
443 }
else if (r->isAsync) {
444 maybeUpdateAsyncItem();
450void QQuickShapeCurveRenderer::maybeUpdateAsyncItem()
452 for (
const PathData &pd : std::as_const(m_paths)) {
453 if (pd.currentRunner && !pd.currentRunner->isDone)
459 m_asyncCallback(m_asyncCallbackData);
464 qDeleteAll(pathData.fillNodes);
465 qDeleteAll(pathData.strokeNodes);
470 QQuickShapeCurveRenderer::processPath(&pathData);
476 static bool d = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DISABLE_STANDARD_DERIVATIVES") != 0;
480void QQuickShapeCurveRenderer::updateNode()
485 auto updateUniforms = [](
const PathData &pathData) {
486 for (
auto &pathNode : std::as_const(pathData.fillNodes)) {
487 if (pathNode->isDebugNode)
489 QSGCurveFillNode *fillNode =
static_cast<QSGCurveFillNode *>(pathNode);
490 fillNode->setColor(pathData.fillColor);
491 fillNode->setGradientType(pathData.gradientType);
492 fillNode->setFillGradient(pathData.gradient);
493 fillNode->setFillTransform(pathData.fillTransform);
494 fillNode->setFillTextureProvider(pathData.fillTextureProviderItem !=
nullptr
495 ? pathData.fillTextureProviderItem->textureProvider()
498 for (QSGCurveAbstractNode *pathNode : std::as_const(pathData.strokeNodes)) {
499 pathNode->setColor(pathData.pen.color());
500 if (pathNode->isDebugNode) {
501 auto *wfNode =
static_cast<QQuickShapeWireFrameNode<StrokeWFT> *>(pathNode);
502 wfNode->setStrokeWidth(pathData.pen.widthF());
503 wfNode->setCosmeticStroke(pathData.pen.isCosmetic());
505 auto *strokeNode =
static_cast<QSGCurveStrokeNode *>(pathNode);
506 strokeNode->setStrokeWidth(pathData.pen.widthF());
507 strokeNode->setCosmeticStroke(pathData.pen.isCosmetic());
512 NodeList toBeDeleted;
514 for (
const PathData &pathData : std::as_const(m_removedPaths)) {
515 toBeDeleted += pathData.fillNodes;
516 toBeDeleted += pathData.strokeNodes;
518 m_removedPaths.clear();
520 const bool supportsDerivatives = m_item !=
nullptr
521 && m_item->window() !=
nullptr
522 && m_item->window()->rhi() !=
nullptr
523 && !disableScreenSpaceDerivativeShader()
524 ? m_item->window()->rhi()->isFeatureSupported(QRhi::ScreenSpaceDerivatives)
527 for (
int i = 0; i < m_paths.size(); i++) {
528 PathData &pathData = m_paths[i];
529 if (pathData.currentRunner) {
530 if (!pathData.currentRunner->isDone)
533 QSGNode *nextNode = pathData.strokeNodes.value(0);
535 for (
int j = i + 1; !nextNode && j < m_paths.size(); j++) {
536 const PathData &pd = m_paths[j];
537 nextNode = pd.fillNodes.isEmpty() ? pd.strokeNodes.value(0) : pd.fillNodes.value(0);
540 PathData &newData = pathData.currentRunner->pathData;
541 if (newData.m_dirty & PathDirty)
542 pathData.path = newData.path;
543 if (newData.m_dirty & FillDirty) {
544 pathData.fillPath = newData.fillPath;
545 for (
auto *node : std::as_const(newData.fillNodes)) {
546 node->setUseStandardDerivatives(supportsDerivatives);
548 m_rootNode->insertChildNodeBefore(node, nextNode);
550 m_rootNode->appendChildNode(node);
552 toBeDeleted += pathData.fillNodes;
553 pathData.fillNodes = newData.fillNodes;
555 if (newData.m_dirty & StrokeDirty) {
556 for (
auto *node : std::as_const(newData.strokeNodes)) {
557 node->setUseStandardDerivatives(supportsDerivatives);
559 m_rootNode->insertChildNodeBefore(node, nextNode);
561 m_rootNode->appendChildNode(node);
563 toBeDeleted += pathData.strokeNodes;
564 pathData.strokeNodes = newData.strokeNodes;
566 if (newData.m_dirty & UniformsDirty)
567 updateUniforms(newData);
570 newData.fillNodes.clear();
571 newData.strokeNodes.clear();
574 if (pathData.currentRunner->isAsync && (pathData.m_dirty & ~UniformsDirty)) {
576 setUpRunner(&pathData);
577 QThreadPool::globalInstance()->start(pathData.currentRunner);
581 pathData.currentRunner->deleteLater();
582 pathData.currentRunner =
nullptr;
586 if (pathData.m_dirty == UniformsDirty && !pathData.currentRunner) {
588 updateUniforms(pathData);
589 pathData.m_dirty = 0;
592 qDeleteAll(toBeDeleted);
595void QQuickShapeCurveRenderer::processPath(PathData *pathData)
597 static const bool doOverlapSolving = !qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER");
598 static const bool doIntersetionSolving = !qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DISABLE_INTERSECTION_SOLVER");
599 static const bool useTriangulatingStroker = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_TRIANGULATING_STROKER");
600 static const bool simplifyPath = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_SIMPLIFY_PATHS");
602 int &dirtyFlags = pathData->m_dirty;
604 if (dirtyFlags & PathDirty) {
606 pathData->path = QQuadPath::fromPainterPath(pathData->originalPath.simplified(), QQuadPath::PathLinear | QQuadPath::PathNonIntersecting | QQuadPath::PathNonOverlappingControlPointTriangles);
608 pathData->path = QQuadPath::fromPainterPath(pathData->originalPath, QQuadPath::PathHints(
int(pathData->pathHints)));
609 pathData->path.setFillRule(pathData->fillRule);
610 pathData->fillPath = {};
611 dirtyFlags |= (FillDirty | StrokeDirty);
614 if (dirtyFlags & FillDirty) {
615 if (pathData->isFillVisible()) {
616 if (pathData->fillPath.isEmpty()) {
617 pathData->fillPath = pathData->path.subPathsClosed();
618 if (doIntersetionSolving)
619 QSGCurveProcessor::solveIntersections(pathData->fillPath);
620 pathData->fillPath.addCurvatureData();
621 if (doOverlapSolving)
622 QSGCurveProcessor::solveOverlaps(pathData->fillPath);
624 pathData->fillNodes = addFillNodes(pathData->fillPath);
625 dirtyFlags |= (StrokeDirty | UniformsDirty);
629 if (dirtyFlags & StrokeDirty) {
630 if (pathData->isStrokeVisible()) {
631 const QPen &pen = pathData->pen;
632 const bool solid = (pen.style() == Qt::SolidLine);
633 const QQuadPath &strokePath = solid ? pathData->path
634 : pathData->path.dashed(pen.widthF(),
637 if (useTriangulatingStroker)
638 pathData->strokeNodes = addTriangulatingStrokerNodes(strokePath, pen);
640 pathData->strokeNodes = addCurveStrokeNodes(strokePath, pen);
645QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(
const QQuadPath &path)
648 std::unique_ptr<QSGCurveFillNode> node(
new QSGCurveFillNode);
649 std::unique_ptr<QQuickShapeWireFrameNode<SimpleWFT>> wfNode;
651 const qsizetype approxDataCount = 20 * path.elementCount();
652 node->reserve(approxDataCount);
654 const int debugFlags = debugVisualization();
655 const bool wireFrame = debugFlags & DebugWireframe;
657 if (Q_LIKELY(!wireFrame)) {
658 QSGCurveProcessor::processFill(path,
660 [&node](
const std::array<QVector2D, 3> &v,
661 const std::array<QVector2D, 3> &n,
662 QSGCurveProcessor::uvForPointCallback uvForPoint)
664 node->appendTriangle(v, n, uvForPoint);
667 QList<QQuickShapeWireFrameNode<SimpleWFT>::WireFrameVertex> wfVertices;
668 wfVertices.reserve(approxDataCount);
669 QSGCurveProcessor::processFill(path,
671 [&wfVertices, &node](
const std::array<QVector2D, 3> &v,
672 const std::array<QVector2D, 3> &n,
673 QSGCurveProcessor::uvForPointCallback uvForPoint)
675 node->appendTriangle(v, n, uvForPoint);
677 wfVertices.append({v.at(0).x(), v.at(0).y(), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f });
678 wfVertices.append({v.at(1).x(), v.at(1).y(), 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f });
679 wfVertices.append({v.at(2).x(), v.at(2).y(), 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f });
682 wfNode.reset(
new QQuickShapeWireFrameNode<SimpleWFT>);
683 const QList<quint32> indices = node->uncookedIndexes();
684 QSGGeometry *wfg =
new QSGGeometry(QQuickShapeWireFrameNode<SimpleWFT>::attributes(),
687 QSGGeometry::UnsignedIntType);
688 wfNode->setGeometry(wfg);
690 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
691 memcpy(wfg->indexData(),
693 indices.size() * wfg->sizeOfIndex());
694 memcpy(wfg->vertexData(),
696 wfg->vertexCount() * wfg->sizeOfVertex());
699 if (Q_UNLIKELY(debugFlags & DebugCurves))
700 node->setDebug(0.5f);
702 if (node->uncookedIndexes().size() > 0) {
703 node->cookGeometry();
704 ret.append(node.release());
706 ret.append(wfNode.release());
712QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(
const QQuadPath &path,
const QPen &pen)
715 const QColor &color = pen.color();
717 QList<QQuickShapeWireFrameNode<StrokeWFT>::WireFrameVertex> wfVertices;
719 QTriangulatingStroker stroker;
720 const auto painterPath = path.toPainterPath();
721 const QVectorPath &vp = qtVectorPathForPath(painterPath);
722 stroker.process(vp, pen, {}, {});
724 auto *node =
new QSGCurveFillNode;
726 auto uvForPoint = [](QVector2D v1, QVector2D v2, QVector2D p)
728 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
730 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
731 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
733 return QVector2D(u, v);
738 auto curveUv = [uvForPoint](QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
740 QVector2D v1 = 2 * (p1 - p0);
741 QVector2D v2 = p2 - v1 - p0;
742 return uvForPoint(v1, v2, p - p0);
745 auto findPointOtherSide = [](
const QVector2D &startPoint,
const QVector2D &endPoint,
const QVector2D &referencePoint){
747 QVector2D baseLine = endPoint - startPoint;
748 QVector2D insideVector = referencePoint - startPoint;
749 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x());
751 bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
753 return swap ? startPoint + normal : startPoint - normal;
756 static bool disableExtraTriangles = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_WIP_DISABLE_EXTRA_STROKE_TRIANGLES");
758 auto addStrokeTriangle = [&](
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3){
759 if (p1 == p2 || p2 == p3) {
763 auto uvForPoint = [&p1, &p2, &p3, curveUv](QVector2D p) {
764 auto uv = curveUv(p1, p2, p3, p);
765 return QVector3D(uv.x(), uv.y(), 0.0f);
768 node->appendTriangle(p1, p2, p3, uvForPoint);
771 wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f});
772 wfVertices.append({p2.x(), p2.y(), 0.0f, 0.1f, 0.0f, 0.0f, 0.0f, 1.0f});
773 wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f});
775 if (!disableExtraTriangles) {
778 QVector2D op = findPointOtherSide(p1, p3, p2);
779 node->appendTriangle(p1, op, p3, uvForPoint);
781 wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f});
782 wfVertices.append({op.x(), op.y(), 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
783 wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f});
787 const int vertCount = stroker.vertexCount() / 2;
788 const float *verts = stroker.vertices();
789 for (
int i = 0; i < vertCount - 2; ++i) {
791 for (
int j = 0; j < 3; ++j) {
792 p[j] = QVector2D(verts[(i+j)*2], verts[(i+j)*2 + 1]);
794 addStrokeTriangle(p[0], p[1], p[2]);
797 QList<quint32> indices = node->uncookedIndexes();
798 if (indices.size() > 0) {
799 node->setColor(color);
801 node->cookGeometry();
804 const bool wireFrame = debugVisualization() & DebugWireframe;
806 QQuickShapeWireFrameNode<StrokeWFT> *wfNode =
new QQuickShapeWireFrameNode<StrokeWFT>;
807 QSGGeometry *wfg =
new QSGGeometry(QQuickShapeWireFrameNode<StrokeWFT>::attributes(),
810 QSGGeometry::UnsignedIntType);
811 wfNode->setGeometry(wfg);
813 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
814 memcpy(wfg->indexData(),
816 indices.size() * wfg->sizeOfIndex());
817 memcpy(wfg->vertexData(),
819 wfg->vertexCount() * wfg->sizeOfVertex());
827void QQuickShapeCurveRenderer::setRootNode(QSGNode *node)
829 clearNodeReferences();
833void QQuickShapeCurveRenderer::clearNodeReferences()
835 for (PathData &pd : m_paths) {
836 pd.fillNodes.clear();
837 pd.strokeNodes.clear();
841int QQuickShapeCurveRenderer::debugVisualizationFlags = QQuickShapeCurveRenderer::NoDebug;
843int QQuickShapeCurveRenderer::debugVisualization()
845 static const int envFlags = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DEBUG");
846 return debugVisualizationFlags | envFlags;
849void QQuickShapeCurveRenderer::setDebugVisualization(
int options)
851 if (debugVisualizationFlags == options)
853 debugVisualizationFlags = options;
857
858
859
860
861QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(
const QQuadPath &path,
const QPen &pen)
865 const bool debug = debugVisualization() & DebugCurves;
866 auto *node =
new QSGCurveStrokeNode;
867 node->setDebug(0.2f * debug);
868 QList<QQuickShapeWireFrameNode<StrokeWFT>::WireFrameVertex> wfVertices;
870 const float penWidth = pen.widthF();
872 static const int subdivisions = qEnvironmentVariable(
"QT_QUICKSHAPES_STROKE_SUBDIVISIONS", QStringLiteral(
"3")).toInt();
874 const bool wireFrame = debugVisualization() & DebugWireframe;
875 QSGCurveProcessor::processStroke(path,
877 penWidth, pen.isCosmetic(),
881 [&wfVertices, &node, &wireFrame](
const std::array<QVector2D, 3> &vtx,
882 const std::array<QVector2D, 3> &ctl,
883 const std::array<QVector2D, 3> &n,
884 const std::array<
float, 3> &ex,
885 QSGCurveStrokeNode::TriangleFlags flags)
887 const QVector2D &v0 = vtx.at(0);
888 const QVector2D &v1 = vtx.at(1);
889 const QVector2D &v2 = vtx.at(2);
890 if (flags.testFlag(QSGCurveStrokeNode::TriangleFlag::Line))
891 node->appendTriangle(vtx, std::array<QVector2D, 2>{ctl.at(0), ctl.at(2)}, n, ex);
893 node->appendTriangle(vtx, ctl, n, ex);
895 if (Q_UNLIKELY(wireFrame)) {
896 wfVertices.append({v0.x(), v0.y(), 1.0f, 0.0f, 0.0f, n.at(0).x(), n.at(0).y(), ex.at(0)});
897 wfVertices.append({v1.x(), v1.y(), 0.0f, 1.0f, 0.0f, n.at(1).x(), n.at(1).y(), ex.at(1)});
898 wfVertices.append({v2.x(), v2.y(), 0.0f, 0.0f, 1.0f, n.at(2).x(), n.at(2).y(), ex.at(2)});
903 auto indexCopy = node->uncookedIndexes();
905 node->setColor(pen.color());
906 node->setStrokeWidth(penWidth);
907 node->setCosmeticStroke(pen.isCosmetic());
908 node->cookGeometry();
911 if (Q_UNLIKELY(wireFrame)) {
912 QQuickShapeWireFrameNode<StrokeWFT> *wfNode =
new QQuickShapeWireFrameNode<StrokeWFT>;
914 QSGGeometry *wfg =
new QSGGeometry(QQuickShapeWireFrameNode<StrokeWFT>::attributes(),
917 QSGGeometry::UnsignedIntType);
918 wfNode->setGeometry(wfg);
919 wfNode->setCosmeticStroke(pen.isCosmetic());
920 wfNode->setStrokeWidth(penWidth);
922 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
923 memcpy(wfg->indexData(),
925 indexCopy.size() * wfg->sizeOfIndex());
926 memcpy(wfg->vertexData(),
928 wfg->vertexCount() * wfg->sizeOfVertex());
void run() override
Implement this pure virtual function in your subclass.
Combined button and popup list for selecting options.
static bool disableScreenSpaceDerivativeShader()