woboq / qmetaobject-rs

Integrate Qml and Rust by building the QMetaObject at compile time.
MIT License
637 stars 89 forks source link

Is it possible to write all-Rust custom scenegraph nodes? #176

Open mikolajsnioch opened 3 years ago

mikolajsnioch commented 3 years ago

The purpose of this issue is educational. I am still learning both Qt and QMetaObject-rs.

I studied the Graph example and tried to recreate Qt's BezierCurve example in Rust (https://doc.qt.io/qt-5/qtquick-scenegraph-customgeometry-example.html). The more I look into it, the more I am convinced that at the moment it is not possible to recreate this example without writing C++, I would love to be wrong, however.

In theory, all this example requires is a Rust struct with reimplemented update_paint_node().

The C++ version of this method is as follows:

QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    QSGGeometryNode *node = nullptr;
    QSGGeometry *geometry = nullptr;

    if (!oldNode) {
        node = new QSGGeometryNode;
        geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
        geometry->setLineWidth(2);
        geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry);
        QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
        material->setColor(QColor(255, 0, 0));
        node->setMaterial(material);
        node->setFlag(QSGNode::OwnsMaterial);
    } else {
        node = static_cast<QSGGeometryNode *>(oldNode);
        geometry = node->geometry();
        geometry->allocate(m_segmentCount);
    }

    QSizeF itemSize = size();
    QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
    for (int i = 0; i < m_segmentCount; ++i) {
        qreal t = i / qreal(m_segmentCount - 1);
        qreal invt = 1 - t;

        QPointF pos = invt * invt * invt * m_p1
                    + 3 * invt * invt * t * m_p2
                    + 3 * invt * t * t * m_p3
                    + t * t * t * m_p4;

        float x = pos.x() * itemSize.width();
        float y = pos.y() * itemSize.height();

        vertices[i].set(x, y);
    }
    node->markDirty(QSGNode::DirtyGeometry);

    return node;
}

Upon reviewing the source of SGNode::update_static and SGNode::update_dynamic I understood that both functions take care of the condition when the oldNode is empty or not empty.

If I understand correctly, I could go past the if statement from the code above directly to drawing. To be able to draw, I would need to have access to geometry. This is where things are becoming less clear to me. I see that there is some code regarding Geometry in the scenegraph module, however it is commented out. Therefore I understand that currently there's no way to create custom Geometry in Rust. If that's true, then I need to follow the same structure as in the Graph example - write my own Nodes in C++ and interface them from Rust.

Is my understanding correct?

Essentially, what I am trying to do, after understanding how it all works and what's possible right now, is to create my own Canvas (a QQuickItem), where I can draw shapes (also QQuickItems - children of the Canvas). I would like to define all these elements in Rust, because I don't really know C++ and C++'s build system is beyond my comprehension at the moment...

I would be happy to help out with exposing Geometry nodes from Qt to Rust, if some guidance was offered.

Thank you

jwintz commented 3 years ago

On educational purposes and discussion. Is there a discord server or whatever the tool to elaborate on qmetaobject-rs whenever issues don’t fit ? That would be awesome.

ratijas commented 3 years ago

On educational purposes and discussion. Is there a discord server or whatever the tool to elaborate on qmetaobject-rs whenever issues don’t fit ? That would be awesome.

I don't think with current pace of development and traction it is feasible to manage a Discord server or whatever separate communication channel. Feel free to use this new GitHub Discussions feature — seems like they were made in attempt to replace IRC/Zulip/Slack style fast-paced talks, while keeping all the GitHub integrations for free.

I [...] tried to recreate Qt's BezierCurve example

By the way, you are looking at Qt 5 docs. For something new and related to graphics internals, it's just about time to start looking into Qt 6. Because they've changed so much (and even introduced Rendering Hardware Interface — additional layer of graphical abstractions) that our Graph example won't even compile anymore: whole classes were just vanished from existence, and it's not viable trying to port it in a compatible way (#168).

With that being said, this particular BezierCurve example doesn't seem any different in Qt 6 version on a first glance, but who knows. I did not follow their change log closely.

at the moment it is not possible to recreate this example without writing C++

Sure, there are some things needed to be exposed in Rust to make it work. On the bright side, most of them are not even QObject subclasses, thus shouldn't be too difficult to wrap around.

Some API just wouldn't make sense in Rust if translated verbatim. For example, QSGGeometry::vertexDataAsPoint2D() returns bare pointer to an array, assuming programmer knows how big this array is. So, it might need additional work to return it from wrapper as a Rust slice. Also, I would make setGeometry and setMaterial to take owned Box of those things, and automatically set QSGNode::Owns{Geometry,Material} flags accordingly.

You could start by cloning Qt repositories and exploring internals; qtbase and qtdeclarative modules are your best friends. Check out _qtdeclarative/src/quick/scenegraph/coreapi/qsggeometry{.h,p.h,.cpp} files for QSGGeometry class implementation. And then wrapping required parts one by one, starting probably from the easiest — enums, then moving onto static functions, and so on.

glhf!

ogoffart commented 3 years ago

right, it would be nice to expose the API in pure rust. However that'd be quite some work to do. volunteers welcome :-)