Написание пользовательского Qt 3D аспекта - часть 1

Qt 3D, OpenGL, C++, Qt

Введение

Qt 3D имеет гибкую и расширяемую архитектуру, которая позволяет нам легко добавлять в нее свои новые функциональные возможности, не нарушая при этом существующие свойства. Функциональность Qt 3D разделена на так называемые аспекты, каждый из которых инкапсулирует конкретную предметную область, такую как рендеринг (Render Aspect), ввод (Input Aspect) или анимация (Animation Aspect).

В этой короткой серии статей вы познакомитесь с процессом добавления нового аспекта, который предоставляет типы компонентов и поведение для новой области, не охваченной Qt 3D из коробки. В этом примере мы решили реализовать аспект, который позволяет рассчитывать текущую среднюю частоту кадров. Конечно, это можно было бы добавить в средство визуализации, но это достаточно просто, чтобы быть хорошим примером для наших сегодняшних целей. Полный исходный код примера доступен для загрузки .

Обзор

Приложение, которое мы соберем, выглядит так. Это - очень простая Qt 3D-сцена, и окно, показывающее текущую среднюю частоту кадров.

Существует несколько частей, которые нам нужно учитывать при добавлении нового функционала:

  • Аспект - отвечает за организацию любых заданий, требующих выполнения каждого фрейма, и за управление хранением любых внешних объектов.
  • Компоненты - предоставляются вашей библиотекой или приложением, которые в совокупности придают конечному объекту новое поведение. Компоненты - это основной API, который вам нужно создавать.
  • Узлы (Nodes на рисунке) - как и для компонентов, за исключением того, что подклассы QNode обычно предоставляют вспомогательные данные, требуемые компонентом. Например QAnimationClipLoader является узлом, который предоставляет ключевые данные анимации кадров компоненту QClipAnimator.
  • Бэкэнд-узлы (Backend Nodes на рисунке) - Бэкэнд-копии для любых внешних компонентов или узлов, предоставляемых вашей библиотекой / приложением. Это объекты, которые обычно обрабатываются заданиями, запущенными в пуле потоков, как это продиктовано самим аспектом.
  • Mapper - Пользовательские карты, которые регистрируются с учетом этого аспекта и отвечают за создание, выборку и уничтожение внутренних узлов по требованию. Mapper используется QAbstractAspect и QAspectEngine для синхронизации времени жизни внешних и внутренних объектов.
  • Задания - создаются и планируются аспектом, обрабатывают базовые узлы. Задания могут также отправлять события во внешние узлы и компоненты, если свойства изменяются.
  • Арбитр изменений - отвечает за доставку событий между фронтэнд и бэкэнд объектами. С ним ничего не нужно делать, но имейте в виду его существование.

Добавление аспекта

Написание исходного аспекта действительно тривиально. Просто унаследуйте QAbstractAspect и зарегистрируйте его с помощью 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 есть виртуальные методы, которые вы можете переопределить для инициализации и удаления, если они вам понадобятся. Однако для этого простого примера все, что нам нужно сделать, это реализовать виртуальный метод jobsToExecute(). Позже мы используем его, чтобы запланировать выполнение задания в пуле потоков на каждый кадр, но сейчас мы просто выводим отладочный текст, чтобы вернуть пустой вектор (нет заданий готовых к выполнению). Обратите внимание, что эти виртуальные функции будут вызываться только после регистрации аспекта с помощью QAspectEngine (см. Выше), чтобы он был частью моделирования. Мы вернемся и завершим этот аспект чуть позже.

Компонент FpsMonitor

Мы хотим добавить функционал, который бы выводил информацию о средней частоте кадров, усредненной по заданному числу кадров. Мы можем написать API-интерфейс примерно так:

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;
};

Обратите внимание, что мы используем декларативный API на основе свойств, чтобы этот класс можно было легко использовать как из QML, так и из C ++. Свойство rollingMeanFrameCount является обычным свойством чтения и записи, а реализация функций setter и getter полностью стандартная. Это свойство будет использоваться для управления количеством кадров, по которым мы вычисляем скользящую среднюю частоту кадров. Свойство framesPerSecond - это свойство только для чтения, которое позже будет установлено из задания, которое мы будем писать для обработки компонентов FpsMonitor в пуле потоков Qt 3D .

Создание небольшого тестового приложения

Прежде чем мы сможем использовать наш пользовательский компонент из QML, мы должны зарегистрировать тип с помощью системы типов QML. Это простой однострочный код, который мы можем добавить к нашей основной функции:

main.cpp

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

где мы также воспользовались возможностью экспортировать окно в QML-контекст (это нам понадобится в определенный момент).

Как только это будет сделано, мы можем импортировать модуль CustomModule QML и использовать компонент FpsMonitor так же, как любой из встроенных типов.

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)"
            }
        }
    ]
}

Конечно, на данный момент FpsMonitor не делает ничего, кроме установки значения свойства rollingMeanFrameCount. Как только мы закончим бэкэнд, приведенный выше код обновит заголовок окна, чтобы показать текущую среднюю частоту кадров и количество кадров, используемых для ее расчета.

В следующей части мы реализуем соответствующий бэкэнд узел для FpsMonitor и убедимся, что он создается и уничтожается по требованию и настраивает связь между интерфейсом и бэкэндом.

Статья написана: Sean Harmer | Суббота, Ноябрь 25, 2017г.

10% refund of hotel reservation amount on Booking
10% refund of hotel reservation amount on Booking
We offer a link with a 10% return on the amount of the order when booking a hotel through Booking

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Looking for a Job?
14,000.00 руб. - 40,000.00 руб.
Разработчик Qt
Annino, Moscow Oblast, Russia
5,000.00 руб. - 15,000.00 руб.
Дизайнер
Moskovskiy, Moscow, Russia
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

For registered users on the site there is a minimum amount of advertising

A
Aug. 22, 2019, 11:24 p.m.
Aleksandr73

Qt - Test 001. Signals and slots

  • Result:47points,
  • Rating points-6
Aug. 21, 2019, 10:23 a.m.
Andrej Ermoshin

C++ - Test 002. Constants

  • Result:58points,
  • Rating points-2
Aug. 21, 2019, 10:15 a.m.
Andrej Ermoshin

C++ - Test 001. The first program and data types

  • Result:86points,
  • Rating points6
Last comments
Aug. 19, 2019, 7:41 a.m.
Andrej Jankovich

это проблема дистрибутива, попробуйте установить через пакетный менеджер snap Суть проблемы: libQt5Core которая лежит в дистрибутиве требует версию glibc >= 2.25 у вас видимо …
b
Aug. 18, 2019, 6:09 a.m.
bbb116

cqtdeployer /home/aleks/CQtDeployer/bin/cqtdeployer: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.25' not found (required by /home/aleks/CQtDeployer/lib/libQt5Core.so.5) linux mint …
D
Aug. 17, 2019, 9:04 a.m.
Damir

github ChekableTView Правой групповая смена значения при перетаскивании левой как обычно.
Aug. 16, 2019, 1:03 p.m.
Evgenij Legotskoj

Потому, что в минуте 60 секунд
Aug. 16, 2019, 12:16 p.m.
Dmitrij

а почему делитель 60000, а не 1000?
Now discuss on the forum
Aug. 24, 2019, 7:21 a.m.
Evgenij Legotskoj

Не помню, давно уже с QML не работал, по-моему, обычно пишет в консоль, что не находит файл. В любом случае какую-то ошибку в консоль выкидывает. Но если честно, если у вас проект будет ак…
BG
Aug. 24, 2019, 4:27 a.m.
Brjus Gliff

Спасибо, вначале в документации было не понятно что к чему, теперь разобрался
I
Aug. 21, 2019, 8:36 a.m.
Intruder

Александр, мне не нужно перебирать. Вы говорите правильно, сначала я написал избыточный код просто не подумав. Задача такая, мне нужно просто переложить из QMap в атрибуты xml тега все, что там …
Aug. 21, 2019, 3:16 a.m.
nayk1982

Если Вы разрабатываете какую-то универсальную утилиту, которая вообще не привязана к логике, тогда как вариант: 1. Получить список таблиц через QSqlDatabase::tables 2. Для каждой табли…
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB