9#include <QtCore/qthreadpool.h>
12#include <QtGui/qvector2d.h>
13#include <QtGui/qvector4d.h>
14#include <QtGui/private/qtriangulator_p.h>
15#include <QtGui/private/qtriangulatingstroker_p.h>
16#include <QtGui/private/qrhi_p.h>
18#include <QtQuick/private/qsgcurvefillnode_p.h>
19#include <QtQuick/private/qsgcurvestrokenode_p.h>
20#include <QtQuick/private/qquadpath_p.h>
21#include <QtQuick/private/qsgcurveprocessor_p.h>
22#include <QtQuick/qsgmaterial.h>
26Q_LOGGING_CATEGORY(lcShapeCurveRenderer,
"qt.shape.curverenderer");
31
32
33
34
35enum WireFrameType { SimpleWFT, StrokeWFT };
37class QQuickShapeWireFrameMaterialShader :
public QSGMaterialShader
40 QQuickShapeWireFrameMaterialShader(WireFrameType wft,
int viewCount) : m_wftype(wft)
42 setShaderFileName(VertexStage, wft == StrokeWFT ?
43 QStringLiteral(
":/qt-project.org/scenegraph/shaders_ng/shapestroke_wireframe.vert.qsb") :
44 QStringLiteral(
":/qt-project.org/shapes/shaders_ng/wireframe.vert.qsb"), viewCount);
45 setShaderFileName(FragmentStage,
47 QStringLiteral(
":/qt-project.org/scenegraph/shaders_ng/shapestroke_wireframe.frag.qsb") :
48 QStringLiteral(
":/qt-project.org/shapes/shaders_ng/wireframe.frag.qsb"), viewCount);
51 bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
override;
53 WireFrameType m_wftype;
56class QQuickShapeWireFrameMaterial :
public QSGMaterial
59 QQuickShapeWireFrameMaterial(WireFrameType wft) : m_wftype(wft)
61 setFlag(Blending,
true);
64 int compare(
const QSGMaterial *other)
const override
66 return (type() - other->type());
69 void setCosmeticStroke(
bool c)
74 void setStrokeWidth(
float width)
76 m_strokeWidth = width;
81 return (m_cosmeticStroke ? -1.0 : 1.0) * qAbs(m_strokeWidth);
85 QSGMaterialType *type()
const override
87 static QSGMaterialType t;
90 QSGMaterialShader *createShader(QSGRendererInterface::RenderMode)
const override
92 return new QQuickShapeWireFrameMaterialShader(m_wftype, viewCount());
95 WireFrameType m_wftype;
96 bool m_cosmeticStroke =
false;
97 float m_strokeWidth = 1.0f;
100bool QQuickShapeWireFrameMaterialShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *)
102 QByteArray *buf = state.uniformData();
103 Q_ASSERT(buf->size() >= 64);
104 const int matrixCount = qMin(state.projectionMatrixCount(), newMaterial->viewCount());
105 bool changed =
false;
106 float localScale = 1.0f;
108 for (
int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
109 if (state.isMatrixDirty()) {
110 QMatrix4x4 m = state.combinedMatrix(viewIndex);
111 if (m_wftype == StrokeWFT)
113 memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
118 const float matrixScale = qSqrt(qAbs(state.determinant())) * state.devicePixelRatio() * localScale;
119 memcpy(buf->data() + matrixCount * 64, &matrixScale, 4);
120 const float dpr = state.devicePixelRatio();
121 memcpy(buf->data() + matrixCount * 64 + 8, &dpr, 4);
122 const float opacity = 1.0;
123 memcpy(buf->data() + matrixCount * 64 + 4, &opacity, 4);
124 const float strokeWidth =
static_cast<QQuickShapeWireFrameMaterial *>(newMaterial)->strokeWidth();
125 memcpy(buf->data() + matrixCount * 64 + 12, &strokeWidth, 4);
132template <WireFrameType wftype>
133class QQuickShapeWireFrameNode :
public QSGCurveAbstractNode
136 struct WireFrameVertex
138 float x, y, u, v, w, nx, ny, sw;
141 QQuickShapeWireFrameNode()
144 setFlag(OwnsGeometry,
true);
145 setGeometry(
new QSGGeometry(attributes(), 0, 0));
149 void setColor(QColor col)
override
154 void setUseStandardDerivatives(
bool useStandardDerivatives) override
156 Q_UNUSED(useStandardDerivatives);
159 void setCosmeticStroke(
bool c)
161 m_material->setCosmeticStroke(c);
164 void setStrokeWidth(
float width)
166 m_material->setStrokeWidth(width);
169 void activateMaterial()
171 m_material.reset(
new QQuickShapeWireFrameMaterial(wftype));
172 setMaterial(m_material.data());
175 static const QSGGeometry::AttributeSet &attributes()
177 static QSGGeometry::Attribute data[] = {
178 QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute),
179 QSGGeometry::Attribute::createWithAttributeType(1, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
180 QSGGeometry::Attribute::createWithAttributeType(2, 3, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute),
182 static QSGGeometry::AttributeSet attrs = { 3,
sizeof(WireFrameVertex), data };
186 void cookGeometry() override
192 QScopedPointer<QQuickShapeWireFrameMaterial> m_material;
196QQuickShapeCurveRenderer::~QQuickShapeCurveRenderer()
198 for (
const PathData &pd : std::as_const(m_paths)) {
199 if (pd.currentRunner) {
200 pd.currentRunner->orphaned =
true;
201 if (!pd.currentRunner->isAsync || pd.currentRunner->isDone)
202 delete pd.currentRunner;
207void QQuickShapeCurveRenderer::beginSync(
int totalCount,
bool *countChanged)
209 if (countChanged !=
nullptr && totalCount != m_paths.size())
210 *countChanged =
true;
211 for (
int i = totalCount; i < m_paths.size(); i++) {
212 setFillTextureProvider(i,
nullptr);
213 m_removedPaths.append(m_paths.at(i));
215 m_paths.resize(totalCount);
218void QQuickShapeCurveRenderer::setPath(
int index,
const QPainterPath &path, QQuickShapePath::PathHints pathHints)
220 auto &pathData = m_paths[index];
221 pathData.originalPath = path;
222 pathData.pathHints = pathHints;
223 pathData.m_dirty |= PathDirty;
226void QQuickShapeCurveRenderer::setStrokeColor(
int index,
const QColor &color)
228 auto &pathData = m_paths[index];
229 const bool wasVisible = pathData.isStrokeVisible();
230 pathData.pen.setColor(color);
231 if (pathData.isStrokeVisible() != wasVisible)
232 pathData.m_dirty |= StrokeDirty;
234 pathData.m_dirty |= UniformsDirty;
237void QQuickShapeCurveRenderer::setStrokeWidth(
int index, qreal w)
239 auto &pathData = m_paths[index];
241 pathData.validPenWidth =
true;
242 pathData.pen.setWidthF(w);
244 pathData.validPenWidth =
false;
246 pathData.m_dirty |= StrokeDirty;
249void QQuickShapeCurveRenderer::setCosmeticStroke(
int index,
bool c)
251 auto &pathData = m_paths[index];
252 pathData.pen.setCosmetic(c);
253 pathData.m_dirty |= StrokeDirty;
256void QQuickShapeCurveRenderer::setFillColor(
int index,
const QColor &color)
258 auto &pathData = m_paths[index];
259 const bool wasVisible = pathData.isFillVisible();
260 pathData.fillColor = color;
261 if (pathData.isFillVisible() != wasVisible)
262 pathData.m_dirty |= FillDirty;
264 pathData.m_dirty |= UniformsDirty;
267void QQuickShapeCurveRenderer::setFillRule(
int index, QQuickShapePath::FillRule fillRule)
269 auto &pathData = m_paths[index];
270 pathData.fillRule = Qt::FillRule(fillRule);
271 pathData.m_dirty |= PathDirty;
274void QQuickShapeCurveRenderer::setJoinStyle(
int index,
275 QQuickShapePath::JoinStyle joinStyle,
278 auto &pathData = m_paths[index];
279 pathData.pen.setJoinStyle(Qt::PenJoinStyle(joinStyle));
280 pathData.pen.setMiterLimit(miterLimit);
281 pathData.m_dirty |= StrokeDirty;
284void QQuickShapeCurveRenderer::setCapStyle(
int index, QQuickShapePath::CapStyle capStyle)
286 auto &pathData = m_paths[index];
287 pathData.pen.setCapStyle(Qt::PenCapStyle(capStyle));
288 pathData.m_dirty |= StrokeDirty;
291void QQuickShapeCurveRenderer::setStrokeStyle(
int index,
292 QQuickShapePath::StrokeStyle strokeStyle,
294 const QList<qreal> &dashPattern)
296 auto &pathData = m_paths[index];
297 pathData.pen.setStyle(Qt::PenStyle(strokeStyle));
298 if (strokeStyle == QQuickShapePath::DashLine) {
299 pathData.pen.setDashPattern(dashPattern);
300 pathData.pen.setDashOffset(dashOffset);
302 pathData.m_dirty |= StrokeDirty;
306 QSGGradientCache::GradientDesc *dst)
308 QGradient::Type gradientType = QGradient::NoGradient;
309 if (
const QQuickShapeLinearGradient *g = qobject_cast<
const QQuickShapeLinearGradient *>(gradient)) {
310 gradientType = QGradient::LinearGradient;
311 dst->a = QPointF(g->x1(), g->y1());
312 dst->b = QPointF(g->x2(), g->y2());
313 }
else if (
const QQuickShapeRadialGradient *g = qobject_cast<
const QQuickShapeRadialGradient *>(gradient)) {
314 gradientType = QGradient::RadialGradient;
315 dst->a = QPointF(g->centerX(), g->centerY());
316 dst->b = QPointF(g->focalX(), g->focalY());
317 dst->v0 = g->centerRadius();
318 dst->v1 = g->focalRadius();
319 }
else if (
const QQuickShapeConicalGradient *g = qobject_cast<
const QQuickShapeConicalGradient *>(gradient)) {
320 gradientType = QGradient::ConicalGradient;
321 dst->a = QPointF(g->centerX(), g->centerY());
322 dst->v0 = g->angle();
323 }
else if (gradient !=
nullptr) {
324 static bool warned =
false;
327 qCWarning(lcShapeCurveRenderer) <<
"Unsupported gradient";
331 if (gradientType != QGradient::NoGradient) {
332 dst->stops = gradient->gradientStops();
333 dst->spread = QGradient::Spread(gradient->spread());
339void QQuickShapeCurveRenderer::setFillGradient(
int index, QQuickShapeGradient *gradient)
341 PathData &pd(m_paths[index]);
342 const bool wasVisible = pd.isFillVisible();
343 pd.gradientType = copyCurveGradient(gradient, &pd.gradient);
344 pd.m_dirty |= (pd.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
347void QQuickShapeCurveRenderer::setStrokeGradient(
int index, QQuickShapeGradient *gradient)
349 PathData &pd(m_paths[index]);
350 const bool wasVisible = pd.isStrokeVisible();
351 pd.strokeGradientType = copyCurveGradient(gradient, &pd.strokeGradient);
352 pd.m_dirty |= (pd.isStrokeVisible() != wasVisible) ? StrokeDirty : UniformsDirty;
355void QQuickShapeCurveRenderer::setFillTransform(
int index,
const QSGTransform &transform)
357 auto &pathData = m_paths[index];
358 pathData.fillTransform = transform;
359 pathData.m_dirty |= UniformsDirty;
362void QQuickShapeCurveRenderer::setFillTextureProvider(
int index, QQuickItem *textureProviderItem)
364 auto &pathData = m_paths[index];
365 const bool wasVisible = pathData.isFillVisible();
366 if (pathData.fillTextureProviderItem !=
nullptr)
367 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
368 pathData.fillTextureProviderItem = textureProviderItem;
369 if (pathData.fillTextureProviderItem !=
nullptr)
370 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(m_item->window());
371 pathData.m_dirty |= (pathData.isFillVisible() != wasVisible) ? FillDirty : UniformsDirty;
374void QQuickShapeCurveRenderer::setVisible(
int index,
bool visible)
376 auto &pathData = m_paths[index];
377 if (pathData.visible == visible)
379 const bool wasFillVisible = pathData.isFillVisible();
380 const bool wasStrokeVisible = pathData.isStrokeVisible();
381 pathData.visible = visible;
382 if (pathData.isFillVisible() != wasFillVisible)
383 pathData.m_dirty |= FillDirty;
384 if (pathData.isStrokeVisible() != wasStrokeVisible)
385 pathData.m_dirty |= StrokeDirty;
388void QQuickShapeCurveRenderer::handleSceneChange(QQuickWindow *window)
390 for (
auto &pathData : m_paths) {
391 if (pathData.fillTextureProviderItem !=
nullptr) {
392 if (window ==
nullptr)
393 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->derefWindow();
395 QQuickItemPrivate::get(pathData.fillTextureProviderItem)->refWindow(window);
400 clearNodeReferences();
403void QQuickShapeCurveRenderer::setAsyncCallback(
void (*callback)(
void *),
void *data)
405 m_asyncCallback = callback;
406 m_asyncCallbackData = data;
409void QQuickShapeCurveRenderer::endSync(
bool async)
411 bool asyncThreadsRunning =
false;
413 for (PathData &pathData : m_paths) {
414 if (!pathData.m_dirty)
417 if (pathData.m_dirty == UniformsDirty) {
422 if (pathData.currentRunner) {
425 if (pathData.currentRunner->isAsync) {
428 asyncThreadsRunning =
true;
432 delete pathData.currentRunner;
433 pathData.currentRunner =
nullptr;
437 pathData.currentRunner =
new QQuickShapeCurveRunnable;
438 setUpRunner(&pathData);
442 pathData.currentRunner->isAsync =
true;
443 QThreadPool::globalInstance()->start(pathData.currentRunner);
444 asyncThreadsRunning =
true;
448 pathData.currentRunner->run();
452 if (async && !asyncThreadsRunning && m_asyncCallback)
453 m_asyncCallback(m_asyncCallbackData);
456void QQuickShapeCurveRenderer::setUpRunner(PathData *pathData)
458 Q_ASSERT(pathData->currentRunner);
459 QQuickShapeCurveRunnable *runner = pathData->currentRunner;
460 runner->isDone =
false;
461 runner->pathData = *pathData;
462 runner->pathData.fillNodes.clear();
463 runner->pathData.strokeNodes.clear();
464 runner->pathData.currentRunner =
nullptr;
465 pathData->m_dirty = 0;
466 if (!runner->isInitialized) {
467 runner->isInitialized =
true;
468 runner->setAutoDelete(
false);
469 QObject::connect(runner, &QQuickShapeCurveRunnable::done, qApp,
470 [
this](QQuickShapeCurveRunnable *r) {
474 }
else if (r->isAsync) {
475 maybeUpdateAsyncItem();
481void QQuickShapeCurveRenderer::maybeUpdateAsyncItem()
483 for (
const PathData &pd : std::as_const(m_paths)) {
484 if (pd.currentRunner && !pd.currentRunner->isDone)
490 m_asyncCallback(m_asyncCallbackData);
495 qDeleteAll(pathData.fillNodes);
496 qDeleteAll(pathData.strokeNodes);
501 QQuickShapeCurveRenderer::processPath(&pathData);
507 static bool d = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DISABLE_STANDARD_DERIVATIVES") != 0;
511void QQuickShapeCurveRenderer::updateNode()
516 auto updateUniforms = [](
const PathData &pathData) {
517 for (
auto &pathNode : std::as_const(pathData.fillNodes)) {
518 if (pathNode->isDebugNode)
520 QSGCurveFillNode *fillNode =
static_cast<QSGCurveFillNode *>(pathNode);
521 fillNode->setColor(pathData.fillColor);
522 fillNode->setGradientType(pathData.gradientType);
523 fillNode->setFillGradient(pathData.gradient);
524 fillNode->setFillTransform(pathData.fillTransform);
525 fillNode->setFillTextureProvider(pathData.fillTextureProviderItem !=
nullptr
526 ? pathData.fillTextureProviderItem->textureProvider()
529 for (QSGCurveAbstractNode *pathNode : std::as_const(pathData.strokeNodes)) {
530 pathNode->setColor(pathData.pen.color());
531 if (pathNode->isDebugNode) {
532 auto *wfNode =
static_cast<QQuickShapeWireFrameNode<StrokeWFT> *>(pathNode);
533 wfNode->setStrokeWidth(pathData.pen.widthF());
534 wfNode->setCosmeticStroke(pathData.pen.isCosmetic());
536 auto *strokeNode =
static_cast<QSGCurveStrokeNode *>(pathNode);
537 strokeNode->setStrokeWidth(pathData.pen.widthF());
538 strokeNode->setCosmeticStroke(pathData.pen.isCosmetic());
539 strokeNode->setStrokeGradient(pathData.strokeGradient);
540 strokeNode->setGradientType(pathData.strokeGradientType);
545 NodeList toBeDeleted;
547 for (
const PathData &pathData : std::as_const(m_removedPaths)) {
548 toBeDeleted += pathData.fillNodes;
549 toBeDeleted += pathData.strokeNodes;
551 m_removedPaths.clear();
553 const bool supportsDerivatives = m_item !=
nullptr
554 && m_item->window() !=
nullptr
555 && m_item->window()->rhi() !=
nullptr
556 && !disableScreenSpaceDerivativeShader()
557 ? m_item->window()->rhi()->isFeatureSupported(QRhi::ScreenSpaceDerivatives)
560 for (
int i = 0; i < m_paths.size(); i++) {
561 PathData &pathData = m_paths[i];
562 if (pathData.currentRunner) {
563 if (!pathData.currentRunner->isDone)
566 QSGNode *nextNode = pathData.strokeNodes.value(0);
568 for (
int j = i + 1; !nextNode && j < m_paths.size(); j++) {
569 const PathData &pd = m_paths[j];
570 nextNode = pd.fillNodes.isEmpty() ? pd.strokeNodes.value(0) : pd.fillNodes.value(0);
573 PathData &newData = pathData.currentRunner->pathData;
574 if (newData.m_dirty & PathDirty)
575 pathData.path = newData.path;
576 if (newData.m_dirty & FillDirty) {
577 pathData.fillPath = newData.fillPath;
578 for (
auto *node : std::as_const(newData.fillNodes)) {
579 node->setUseStandardDerivatives(supportsDerivatives);
581 m_rootNode->insertChildNodeBefore(node, nextNode);
583 m_rootNode->appendChildNode(node);
585 toBeDeleted += pathData.fillNodes;
586 pathData.fillNodes = newData.fillNodes;
588 if (newData.m_dirty & StrokeDirty) {
589 for (
auto *node : std::as_const(newData.strokeNodes)) {
590 node->setUseStandardDerivatives(supportsDerivatives);
592 m_rootNode->insertChildNodeBefore(node, nextNode);
594 m_rootNode->appendChildNode(node);
596 toBeDeleted += pathData.strokeNodes;
597 pathData.strokeNodes = newData.strokeNodes;
599 if (newData.m_dirty & UniformsDirty)
600 updateUniforms(pathData);
603 newData.fillNodes.clear();
604 newData.strokeNodes.clear();
607 if (pathData.currentRunner->isAsync && (pathData.m_dirty & ~UniformsDirty)) {
609 setUpRunner(&pathData);
610 QThreadPool::globalInstance()->start(pathData.currentRunner);
614 pathData.currentRunner->deleteLater();
615 pathData.currentRunner =
nullptr;
619 if (pathData.m_dirty == UniformsDirty && !pathData.currentRunner) {
621 updateUniforms(pathData);
622 pathData.m_dirty = 0;
625 qDeleteAll(toBeDeleted);
628void QQuickShapeCurveRenderer::processPath(PathData *pathData)
630 static const bool doOverlapSolving = !qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DISABLE_OVERLAP_SOLVER");
631 static const bool doIntersetionSolving = !qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DISABLE_INTERSECTION_SOLVER");
632 static const bool useTriangulatingStroker = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_TRIANGULATING_STROKER");
633 static const bool simplifyPath = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_SIMPLIFY_PATHS");
635 int &dirtyFlags = pathData->m_dirty;
637 if (dirtyFlags & PathDirty) {
639 pathData->path = QQuadPath::fromPainterPath(pathData->originalPath.simplified(), QQuadPath::PathLinear | QQuadPath::PathNonIntersecting | QQuadPath::PathNonOverlappingControlPointTriangles);
641 pathData->path = QQuadPath::fromPainterPath(pathData->originalPath, QQuadPath::PathHints(
int(pathData->pathHints)));
642 pathData->path.setFillRule(pathData->fillRule);
643 pathData->fillPath = {};
644 dirtyFlags |= (FillDirty | StrokeDirty);
647 if (dirtyFlags & FillDirty) {
648 if (pathData->isFillVisible()) {
649 if (pathData->fillPath.isEmpty()) {
650 pathData->fillPath = pathData->path.subPathsClosed();
651 if (doIntersetionSolving)
652 QSGCurveProcessor::solveIntersections(pathData->fillPath);
653 pathData->fillPath.addCurvatureData();
654 if (doOverlapSolving)
655 QSGCurveProcessor::solveOverlaps(pathData->fillPath);
657 pathData->fillNodes = addFillNodes(pathData->fillPath);
658 dirtyFlags |= (StrokeDirty | UniformsDirty);
662 if (dirtyFlags & StrokeDirty) {
663 if (pathData->isStrokeVisible()) {
664 const QPen &pen = pathData->pen;
665 const bool solid = (pen.style() == Qt::SolidLine);
666 const QQuadPath &strokePath = solid ? pathData->path
667 : pathData->path.dashed(pen.widthF(),
670 if (useTriangulatingStroker)
671 pathData->strokeNodes = addTriangulatingStrokerNodes(strokePath, pen);
673 pathData->strokeNodes = addCurveStrokeNodes(strokePath, pen);
674 dirtyFlags |= UniformsDirty;
679QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addFillNodes(
const QQuadPath &path)
682 std::unique_ptr<QSGCurveFillNode> node(
new QSGCurveFillNode);
683 std::unique_ptr<QQuickShapeWireFrameNode<SimpleWFT>> wfNode;
685 const qsizetype approxDataCount = 20 * path.elementCount();
686 node->reserve(approxDataCount);
688 const int debugFlags = debugVisualization();
689 const bool wireFrame = debugFlags & DebugWireframe;
691 if (Q_LIKELY(!wireFrame)) {
692 QSGCurveProcessor::processFill(path,
694 [&node](
const std::array<QVector2D, 3> &v,
695 const std::array<QVector2D, 3> &n,
696 QSGCurveProcessor::uvForPointCallback uvForPoint)
698 node->appendTriangle(v, n, uvForPoint);
701 QList<QQuickShapeWireFrameNode<SimpleWFT>::WireFrameVertex> wfVertices;
702 wfVertices.reserve(approxDataCount);
703 QSGCurveProcessor::processFill(path,
705 [&wfVertices, &node](
const std::array<QVector2D, 3> &v,
706 const std::array<QVector2D, 3> &n,
707 QSGCurveProcessor::uvForPointCallback uvForPoint)
709 node->appendTriangle(v, n, uvForPoint);
711 wfVertices.append({v.at(0).x(), v.at(0).y(), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f });
712 wfVertices.append({v.at(1).x(), v.at(1).y(), 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f });
713 wfVertices.append({v.at(2).x(), v.at(2).y(), 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f });
716 wfNode.reset(
new QQuickShapeWireFrameNode<SimpleWFT>);
717 const QList<quint32> indices = node->uncookedIndexes();
718 QSGGeometry *wfg =
new QSGGeometry(QQuickShapeWireFrameNode<SimpleWFT>::attributes(),
721 QSGGeometry::UnsignedIntType);
722 wfNode->setGeometry(wfg);
724 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
725 memcpy(wfg->indexData(),
727 indices.size() * wfg->sizeOfIndex());
728 memcpy(wfg->vertexData(),
730 wfg->vertexCount() * wfg->sizeOfVertex());
733 if (Q_UNLIKELY(debugFlags & DebugCurves))
734 node->setDebug(0.5f);
736 if (node->uncookedIndexes().size() > 0) {
737 node->cookGeometry();
738 ret.append(node.release());
740 ret.append(wfNode.release());
746QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addTriangulatingStrokerNodes(
const QQuadPath &path,
const QPen &pen)
749 const QColor &color = pen.color();
751 QList<QQuickShapeWireFrameNode<StrokeWFT>::WireFrameVertex> wfVertices;
753 QTriangulatingStroker stroker;
754 const auto painterPath = path.toPainterPath();
755 const QVectorPath &vp = qtVectorPathForPath(painterPath);
756 stroker.process(vp, pen, {}, {});
758 auto *node =
new QSGCurveFillNode;
760 auto uvForPoint = [](QVector2D v1, QVector2D v2, QVector2D p)
762 double divisor = v1.x() * v2.y() - v2.x() * v1.y();
764 float u = (p.x() * v2.y() - p.y() * v2.x()) / divisor;
765 float v = (p.y() * v1.x() - p.x() * v1.y()) / divisor;
767 return QVector2D(u, v);
772 auto curveUv = [uvForPoint](QVector2D p0, QVector2D p1, QVector2D p2, QVector2D p)
774 QVector2D v1 = 2 * (p1 - p0);
775 QVector2D v2 = p2 - v1 - p0;
776 return uvForPoint(v1, v2, p - p0);
779 auto findPointOtherSide = [](
const QVector2D &startPoint,
const QVector2D &endPoint,
const QVector2D &referencePoint){
781 QVector2D baseLine = endPoint - startPoint;
782 QVector2D insideVector = referencePoint - startPoint;
783 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x());
785 bool swap = QVector2D::dotProduct(insideVector, normal) < 0;
787 return swap ? startPoint + normal : startPoint - normal;
790 static bool disableExtraTriangles = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_WIP_DISABLE_EXTRA_STROKE_TRIANGLES");
792 auto addStrokeTriangle = [&](
const QVector2D &p1,
const QVector2D &p2,
const QVector2D &p3){
793 if (p1 == p2 || p2 == p3) {
797 auto uvForPoint = [&p1, &p2, &p3, curveUv](QVector2D p) {
798 auto uv = curveUv(p1, p2, p3, p);
799 return QVector3D(uv.x(), uv.y(), 0.0f);
802 node->appendTriangle(p1, p2, p3, uvForPoint);
805 wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f});
806 wfVertices.append({p2.x(), p2.y(), 0.0f, 0.1f, 0.0f, 0.0f, 0.0f, 1.0f});
807 wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f});
809 if (!disableExtraTriangles) {
812 QVector2D op = findPointOtherSide(p1, p3, p2);
813 node->appendTriangle(p1, op, p3, uvForPoint);
815 wfVertices.append({p1.x(), p1.y(), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f});
816 wfVertices.append({op.x(), op.y(), 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
817 wfVertices.append({p3.x(), p3.y(), 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f});
821 const int vertCount = stroker.vertexCount() / 2;
822 const float *verts = stroker.vertices();
823 for (
int i = 0; i < vertCount - 2; ++i) {
825 for (
int j = 0; j < 3; ++j) {
826 p[j] = QVector2D(verts[(i+j)*2], verts[(i+j)*2 + 1]);
828 addStrokeTriangle(p[0], p[1], p[2]);
831 QList<quint32> indices = node->uncookedIndexes();
832 if (indices.size() > 0) {
833 node->setColor(color);
835 node->cookGeometry();
838 const bool wireFrame = debugVisualization() & DebugWireframe;
840 QQuickShapeWireFrameNode<StrokeWFT> *wfNode =
new QQuickShapeWireFrameNode<StrokeWFT>;
841 QSGGeometry *wfg =
new QSGGeometry(QQuickShapeWireFrameNode<StrokeWFT>::attributes(),
844 QSGGeometry::UnsignedIntType);
845 wfNode->setGeometry(wfg);
847 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
848 memcpy(wfg->indexData(),
850 indices.size() * wfg->sizeOfIndex());
851 memcpy(wfg->vertexData(),
853 wfg->vertexCount() * wfg->sizeOfVertex());
861void QQuickShapeCurveRenderer::setRootNode(QSGNode *node)
863 clearNodeReferences();
867void QQuickShapeCurveRenderer::clearNodeReferences()
869 for (PathData &pd : m_paths) {
870 pd.fillNodes.clear();
871 pd.strokeNodes.clear();
875int QQuickShapeCurveRenderer::debugVisualizationFlags = QQuickShapeCurveRenderer::NoDebug;
877int QQuickShapeCurveRenderer::debugVisualization()
879 static const int envFlags = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_DEBUG");
880 return debugVisualizationFlags | envFlags;
883void QQuickShapeCurveRenderer::setDebugVisualization(
int options)
885 if (debugVisualizationFlags == options)
887 debugVisualizationFlags = options;
891
892
893
894
895QQuickShapeCurveRenderer::NodeList QQuickShapeCurveRenderer::addCurveStrokeNodes(
const QQuadPath &path,
const QPen &pen)
899 const bool debug = debugVisualization() & DebugCurves;
900 auto *node =
new QSGCurveStrokeNode;
901 node->setDebug(0.2f * debug);
902 QList<QQuickShapeWireFrameNode<StrokeWFT>::WireFrameVertex> wfVertices;
904 const float penWidth = pen.widthF();
906 static const int subdivisions = qEnvironmentVariable(
"QT_QUICKSHAPES_STROKE_SUBDIVISIONS", QStringLiteral(
"3")).toInt();
908 const bool wireFrame = debugVisualization() & DebugWireframe;
909 QSGCurveProcessor::processStroke(path,
911 penWidth, pen.isCosmetic(),
915 [&wfVertices, &node, &wireFrame](
const std::array<QVector2D, 3> &vtx,
916 const std::array<QVector2D, 3> &ctl,
917 const std::array<QVector2D, 3> &n,
918 const std::array<
float, 3> &ex,
919 QSGCurveStrokeNode::TriangleFlags flags)
921 const QVector2D &v0 = vtx.at(0);
922 const QVector2D &v1 = vtx.at(1);
923 const QVector2D &v2 = vtx.at(2);
924 if (flags.testFlag(QSGCurveStrokeNode::TriangleFlag::Line))
925 node->appendTriangle(vtx, std::array<QVector2D, 2>{ctl.at(0), ctl.at(2)}, n, ex);
927 node->appendTriangle(vtx, ctl, n, ex);
929 if (Q_UNLIKELY(wireFrame)) {
930 wfVertices.append({v0.x(), v0.y(), 1.0f, 0.0f, 0.0f, n.at(0).x(), n.at(0).y(), ex.at(0)});
931 wfVertices.append({v1.x(), v1.y(), 0.0f, 1.0f, 0.0f, n.at(1).x(), n.at(1).y(), ex.at(1)});
932 wfVertices.append({v2.x(), v2.y(), 0.0f, 0.0f, 1.0f, n.at(2).x(), n.at(2).y(), ex.at(2)});
937 auto indexCopy = node->uncookedIndexes();
939 node->setColor(pen.color());
940 node->setStrokeWidth(penWidth);
941 node->setCosmeticStroke(pen.isCosmetic());
942 node->cookGeometry();
945 if (Q_UNLIKELY(wireFrame)) {
946 QQuickShapeWireFrameNode<StrokeWFT> *wfNode =
new QQuickShapeWireFrameNode<StrokeWFT>;
948 QSGGeometry *wfg =
new QSGGeometry(QQuickShapeWireFrameNode<StrokeWFT>::attributes(),
951 QSGGeometry::UnsignedIntType);
952 wfNode->setGeometry(wfg);
953 wfNode->setCosmeticStroke(pen.isCosmetic());
954 wfNode->setStrokeWidth(penWidth);
956 wfg->setDrawingMode(QSGGeometry::DrawTriangles);
957 memcpy(wfg->indexData(),
959 indexCopy.size() * wfg->sizeOfIndex());
960 memcpy(wfg->vertexData(),
962 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()
static QGradient::Type copyCurveGradient(const QQuickShapeGradient *gradient, QSGGradientCache::GradientDesc *dst)