I've seen there is an example implementation of a Bezier curve in QML, but I'm looking for a hint how to implement dashed or dotted bezier curve line. As far as I see, tha authors of Bezier curve example are using QSGGeometryNode
to store inside QSGGeometry
with a QSGFlatColorMaterial
material applied on it. Then they simply create list of points and draw segments between them.
Is it possible to write a shader
and apply it to QSGFlatColorMaterial
(to display line as dashed
, dotted
, etc)?
Eventually, is it possible to store more than one QSGGeometry
inside QSGGeometryNode
?
UPDATE
I would like to implement this in "pure QtQuick
" - not in "old" interfaces (like QPainter etc
) - because I do not want to use something, which switches context (openGL and CPU). I prefer the solution with custom shader (if is it doable) - because I'll have more possibilities in implementing custom look and feel (dashed, doted, colored, maybe animated etc).
If it is not possible, I'll use QPainter
.
I don't think this task is a good candidate for implementing using QSGGeometryNode
, it would be much easier to implement it using QPainter
based drawing and a QQuickPaintedItem
. You will still get the benefits of OpenGL, since QPainter
supports GL drawing as well and it is still faster than software. You can use the stock QPen
with stock dotted or dashed patterns or make your own with a simple QVector
.
Alternatively, you can go for a custom GL drawing approach instead of using the classes provided by Qt, which are pretty limited when it comes to representing advanced compound geometry. You can even use instancing (if available) to improve performance even further, and just position dashes or dot geometry along the path curve.
Last but not least, you can use a QML Canvas element, which supports pretty much the same operations as QPainter
and probably offers the same performance.
EDIT: As your update suggests, you missed the part where I said QPainter
can draw in both software and GL, with GL drawing being often significantly faster. Also, by drawing to a GL context, you don't have to move the framebuffer from CPU to GPU memory, it is kept in GPU memory. So no overhead. As for animations and the other stuff, sure, it is not possible with QPainter
you are limited to whatever QPen
provides - different joins, caps and so on can be used to modify the shape to some extent, but no miracles... It won't be possible with shaders too, it will only be possible with custom geometry. And if you use a QObject
based object for each dash/dot element in order to independently animate them, it will end up quite expenssive, QObject
is very heavy and should not be used with such light hand. So custom GL rendering to a FBO is pretty much the way to go if you want that kind of flexibility, but you will have to move completely out of the QtQuick API and into GL land.
At any rate, a dashed line shader should not be all that complex, basically you color the fragment based on the distance from the curve and the "period" along its length. I found this example, haven't tried it myself. You could animate the thresholds, even use a sine function to get funky looking styling.
As for a "pure" QtQuick implementation, the API has not really been designed to handle such type of drawing tasks, that is why the Canvas element was provided to fill the gap and get advanced paining functionality from QML/JS. The Canvas is effectively a wrapper around QPainter
that draws onto a FBO.
In the end it doesn't boil down to what is possible/impossible but which approach makes the most sense and is most efficient at getting the job done. Try the QQuickPaintedItem
approach first, since it is the easiest, if you are not happy with performance, you can implement another more complex solution and profile against the first. After all, that is why QQuickPaintedItem
was introduced in the first place - to handle legacy painting that is not convenient to do with the QQuickItem
class.
With Qt 5.10 the Shape element has been introduced and seems to do exactly what you want.
https://doc.qt.io/qt-5.10/qml-qtquick-shapes-shape.html
Shape {
width: 20
ShapePath {
strokeColor: "blue"
strokeWidth: 2
strokeStyle: ShapePath.DashLine
startX: 0
startY: 0
PathLine { x: parent.width; y: 0 }
}
}
Why don't you use this approach:
Beziercurve example modified to use QSGVertexColorMaterial
Then, you can specify your color and alpha for each vetex, to get the dashes, dots, or whatever pattern you choose.
Here are the important parts:
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), m_segmentCount);
//geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
QSGVertexColorMaterial *material = new QSGVertexColorMaterial;
//material->setColor(QColor(255, 0, 0));
//QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
QSGGeometry::ColoredPoint2D *vertices = geometry->vertexDataAsColoredPoint2D();
vertices[i].set(x, y, 0, 0, 0, 0);
//vertices[i].set(x, y);
No, you can not store multiple geometries in a geometry node. The API is very explicit about it. There's no reason to store multiple geometries there, since a node pairs a geometry and a material. You can reuse geometries and materials between nodes - that's how it's intended to be used, in fact.
The rest of the question is not really complete, and even if a shader-based implementation was provided, it won't be very useful initially. It'll be merely a premature optimization. Let's look at what you're missing.
The example BezierCurve item is just a proof of concept. It's not useful by itself, since you need a way of chaining multiple items that are stroked using the same pen. You need something akin to a simple QPainterPath
. In fact, the geometry itself could be generated by the QPainterPath
and QPainterPathStroker
.
Once you've got a complete tesselated geometry for a continuously-stroked item, you can then either further cut it up based on the line style, or use a shader. It'd require profiling to show that a line-style shader by itself is a big win. It may well be that you need a geometry shader to do the stroking etc. and all the performance gain would be centered there. Think of the number of computations to be done, the line-styling is relatively simple.
import QtQuick 2.0
Rectangle {
width : 1024
height: 600
Rectangle {
x: -3 + 158
y: 355
width: 4; height: 4;
color: "black";
}
Rectangle {
x: 359 + 158
y: 220
width: 4; height: 4;
color: "black";
}
Rectangle {
x: 175 + 158
y: 238
width: 2; height: 2;
color: "black";
}
Rectangle {
x: 711 + 158
y: 355
width: 4; height: 4;
color: "black";
}
Rectangle {
x: 533 + 158
y: 238
width: 2; height: 2;
color: "black";
}
Rectangle {
x: -3 + 118
y: 355
width: 4; height: 4;
color: "darkBlue";
}
Rectangle {
x: 399 + 118
y: 220
width: 4; height: 4;
color: "darkBlue";
}
Rectangle {
x: 196 + 118
y: 238
width: 4; height: 4;
color: "darkBlue";
}
Rectangle {
x: 791 + 118
y: 355
width: 4; height: 4;
color: "darkBlue";
}
Rectangle {
x: 592 + 118
y: 238
width: 4; height: 4;
color: "darkBlue";
}
Path {
id: path
startX: -3
startY: 355
PathQuad { x: 359; y:220; controlX: 175; controlY:238 }
PathQuad { x: 711; y:355; controlX: 533; controlY:238 }
}
Path {
id: path2
startX: -3
startY: 355
PathQuad { x: 399; y:220; controlX: 196; controlY:238 }
PathQuad { x: 791; y:355; controlX: 592; controlY:238 }
}
PathView {
id: pathView;
x: 158
width: 708
model: 300;
path: path
delegate: Rectangle {
id: dot;
width: 1; height: 1;
color: "red";
}
}
PathView {
id: pathView2;
x: 118
width: 788
model: 300;
path: path2
delegate: Rectangle {
id: dot2;
width: 1; height: 1;
color: "green";
}
}
}