Writing a custom Qt 3D aspect - part 1

Introduction

Qt 3D has a flexible and extensible architecture that allows us to easily add new functionality to it without breaking existing features. The functionality of Qt 3D is divided into so-called aspects, each of which encapsulates a specific subject area, such as rendering (Render Aspect), input (Input Aspect), or animation (Animation Aspect).

In this short series of articles, you'll walk through the process of adding a new aspect that provides component types and behavior for a new area not covered by Qt 3D out of the box. In this example, we decided to implement an aspect that allows us to calculate the current average frame rate. Of course, this could be added to the renderer, but it's simple enough to be a good example for our purposes today. The full source code for the example is available for download .


Review

The application we will build looks like this. This is a very simple Qt 3D scene, and a window showing the current average frame rate.

There are several parts that we need to consider when adding new functionality:

  • Aspect - is responsible for organizing any tasks that require the execution of each frame, and for managing the storage of any external objects.
  • Components - Provided by your library or application, which together give the final object a new behavior. Components are the main API you need to create.
  • Nodes (Nodes in the figure) - as for components, except that subclasses of QNode usually provide the ancillary data required by the component. For example, QAnimationClipLoader is a node that provides key frame animation data to the QClipAnimator component.
  • Backend Nodes (Backend Nodes in the picture) - Backend copies for any external components or nodes provided by your library/application. These are objects that are typically handled by jobs running on the thread pool, as dictated by the aspect itself.
  • Mapper - Custom maps that register with this aspect in mind and are responsible for creating, fetching and destroying internal nodes on demand. Mapper is used by QAbstractAspect and QAspectEngine to synchronize the lifetime of external and internal objects.
  • Jobs - created and scheduled by the aspect, process the base nodes. Jobs can also send events to external nodes and components if properties change.
  • Change arbiter - responsible for delivering events between frontend and backend objects. You don't have to do anything with it, but be aware of its existence.

Adding an aspect

Writing the original aspect is really trivial. Just inherit QAbstractAspect and register it with QAspectEngine:

customaspect.h

class CustomAspect : public Qt3DCore::QAbstractAspect
{
    Q_OBJECT
public:
    explicit CustomAspect(QObject *parent = nullptr)
        : Qt3DCore::QAbstractAspect(parent)
    {}

protected:
    QVector<Qt3DCore::QAspectJobPtr> jobsToExecute(qint64 time) override
    {
        qDebug() << Q_FUNC_INFO << "Frame time =" << time;
        return {};
    }
};

main.cpp

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);
    Qt3DExtras::Quick::Qt3DQuickWindow view;

    view.engine()->aspectEngine()->registerAspect(new CustomAspect);

    view.setSource(QUrl("qrc:/main.qml"));
    view.show();
    return app.exec();
}

QAbstractAspect has virtual methods that you can override for initialization and deletion if you need them. However, for this simple example, all we need to do is implement the virtual method jobsToExecute(). We'll use it later to schedule a job on the thread pool every frame, but for now we're just printing debug text to return an empty vector (no jobs ready to run). Note that these virtual functions will only be called once the aspect has been registered with the QAspectEngine (see above) to be part of the simulation. We'll come back and finish this aspect a bit later.

FpsMonitor component

We want to add functionality that would display information about the average frame rate, averaged over a given number of frames. We can write an API like this:

fpsmonitor.h

class FpsMonitor : public Qt3DCore::QComponent
{
    Q_OBJECT
    Q_PROPERTY(int rollingMeanFrameCount READ rollingMeanFrameCount WRITE setRollingMeanFrameCount NOTIFY rollingMeanFrameCountChanged)
    Q_PROPERTY(float framesPerSecond READ framesPerSecond NOTIFY framesPerSecondChanged)

public:
    explicit FpsMonitor(Qt3DCore::QNode *parent = nullptr);

    float framesPerSecond() const;
    int rollingMeanFrameCount() const;

public slots:
    void setRollingMeanFrameCount(int rollingMeanFrameCount);

signals:
    void framesPerSecondChanged(float framesPerSecond);
    void rollingMeanFrameCountChanged(int rollingMeanFrameCount);

private:
    float m_framesPerSecond;
    int m_rollingMeanFrameCount;
};

Please note that we are using a property-based declarative API so that this class can be easily used from both QML and C++. The rollingMeanFrameCount property is a normal read/write property, and the implementation of the setter and getter functions is completely standard. This property will be used to control the number of frames over which we calculate the moving average frame rate. The framesPerSecond property is a read-only property that will later be set from a job we'll be writing to process FpsMonitor components in the Qt 3D thread pool.

Create a small test application

Before we can use our custom component from QML, we must register the type with the QML type system. This is a simple one line code that we can add to our main function:

main.cpp

qmlRegisterType<FpsMonitor>("CustomModule", 1, 0, "FpsMonitor");
rootContext->setContextProperty("_window", &view);

where we also took advantage of the ability to export the window to a QML context (we will need this at some point).

Once this is done, we can import the CustomModule QML module and use the FpsMonitor component just like any of the built-in types.

main.qml

Entity {
    components: [
        FpsMonitor {
            rollingMeanFrameCount: 20
            onFramesPerSecondChanged: {
                var fpsString = parseFloat(Math.round(framesPerSecond * 100) / 100).toFixed(2);
                _window.title = "CustomAspect: FPS = " + fpsString
                        + " (" + rollingMeanFrameCount + " frame average)"
            }
        }
    ]
}

Of course, at the moment FpsMonitor does nothing but set the value of the rollingMeanFrameCount property. Once we've finished the backend, the above code will update the window title to show the current average frame rate and the number of frames used to calculate it.

In the next part, we will implement the appropriate backend node for FpsMonitor and make sure it is created and destroyed on demand and sets up communication between the frontend and the backend.

Article written by: Sean Harmer | Saturday, November 25, 2017

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

Comments

Only authorized users can post comments.
Please, Log in or Sign up
г
  • ги
  • April 24, 2024, 3:51 a.m.

C++ - Test 005. Structures and Classes

  • Result:41points,
  • Rating points-8
l
  • laei
  • April 23, 2024, 9:19 p.m.

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:10points,
  • Rating points-10
l
  • laei
  • April 23, 2024, 9:17 p.m.

C++ - Тест 003. Условия и циклы

  • Result:50points,
  • Rating points-4
Last comments
k
kmssrFeb. 9, 2024, 7:43 a.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 11:30 p.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 9:38 p.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 19, 2023, 10:01 a.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
G
GarApril 22, 2024, 5:46 p.m.
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil AcademicsApril 20, 2024, 7:45 p.m.
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasovApril 14, 2024, 6:41 p.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел ДорофеевApril 14, 2024, 2:35 p.m.
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrexApril 4, 2024, 4:47 p.m.
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Follow us in social networks