Écrire son propre aspect pour Qt 3D

Partie 1 : étendre Qt 3D par des aspects

| Sean Harmer | Alexandre Laurent |

Traducteur

Théodore Prévot

Qt 3D possède une architecture aussi flexible qu'extensible qui nous permet d'ajouter facilement nos propres fonctionnalités sans perturber celles existantes. Les fonctionnalités de Qt 3D se divisent en plusieurs aspects, chacun d'eux encapsulant un domaine spécifique tels le rendu, les entrées ou encore l'animation.

Cette courte série d'articles vous montrera la marche à suivre pour tout savoir sur l'ajout de nouveaux éléments qui fourniraient des types de composants et de comportements pour un nouveau domaine non couvert par Qt 3D. Pour cet exemple, nous avons choisi d'implémenter un aspect qui permettra de calculer le nombre d'images par seconde en temps réel. Bien sûr, cela pourrait être ajouté dans le moteur de rendu, mais cela suffit comme exemple pour nos objectifs d'aujourd'hui. Le code source du projet est disponible en téléchargement.

Commentez

- Titre : Écrire son propre aspect pour Qt 3D

- Auteurs : Sean Harmer, Alexandre Laurent

- Traducteur : Théodore Prévot

- Parution : 2 janvier 2018

- Mise à jour : 15 février 2018

- Licence : Copyright ® 2018 Théodore Prévot. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Aperçu

- II. Ajouter un aspect

- III. Composant FpsMonitor

- IV. Création d'une petite application de test

- VI. Remerciements

I - Aperçu

L'application que nous allons créer ressemblera à ça. C'est une scène Qt 3D vraiment très simple dont la fenêtre montre la moyenne du nombre d'images par seconde en direct.

Il y a quelques parties que nous devrons prendre en compte lors de l'ajout de cette nouvelle fonctionnalité (voir le diagramme) :

- aspect (aspect) – responsable de l'organisation des tâches qui doivent être exécutées à chaque rafraîchissement  ; il gère le stockage de tous les objets en arrière-plan ;

- composant (component) – au premier plan, il est fourni par votre bibliothèque/application, une entité que l'on définira pour lui donner un nouveau comportement. Les composants sont les principales API que vous devrez créer ;

- nœud (node) – a le même fonctionnement qu'un composant au premier plan, si ce n'est que les sous-classes de QNode sont généralement fournies avec les données requises par un composant. Par exemple, QAnimationClipLoader est un nœud qui fournit des images clés d'animation à un composant QClipAnimator ;

- nœud d'arrière-plan et composant de calcul (backend node/backend component) – équivalent des nœuds et composants de premier plan fournis par votre bibliothèque/application, mais pour les calculs. Ce sont des objets généralement traités par les tâches en cours d'exécution, comme le veut notre propre aspect ;

- mapper – le mapper personnalisé est enregistré auprès de son aspect et est responsable de la création, de la capture et de la destruction des nœuds de calcul lorsque qu'il y a lieu. Le mapper est utilisé par QAbstractAspect et QAspectEngine pour synchroniser les durées de vie des objets de premier plan et de calcul ;

- tâches (jobs) – créées et planifiées par l'aspect et par lesquelles il traite les nœuds d'arrière-plan. Les tâches peuvent aussi envoyer des signaux aux nœuds de premier plan et aux composants si leurs propriétés changent ;

- arbitre des changements (changes arbiter) – responsable de la distribution des signaux entre les objets de premier plan et d'arrière-plan. Il n'y a pas besoin de faire quoi que ce soit avec cet objet, mais il faut être conscient de son existence.

II - Ajouter un aspect

Écrire l'aspect initial soi-même est d'une simplicité enfantine. Il vous faut juste prendre la sous-classe QAbstractAspect et l'enregistrer avec QAspectEngine comme ci-dessous :

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 possède certaines fonctions que vous pouvez utiliser afin de l'initialiser et de le réinitialiser. Toutefois, pour cet exemple très simple, tout ce qu'il nous faut, c'est implémenter la fonction virtuelle jobsToExecute. Plus tard, nous l'utiliserons pour programmer une tâche à exécuter dans le groupe de fils d'exécution à chaque rafraîchissement, mais pour l'instant contentons-nous d'écrire un peu de texte de débogage lorsqu'un vecteur nul est renvoyé (pas de tâche à lancer). Notez que cette fonction virtuelle sera appelée lorsque vous aurez enregistré l'aspect avec QAspectEngine (voir ci-dessous) pour qu'elle fasse partie de la simulation.

III - Composant FpsMonitor

Nous souhaitons ajouter des fonctionnalités pour signaler la moyenne du nombre d'images par seconde sur un nombre donné de rafraîchissements. C'est dans cette optique que nous pouvons concevoir une API comme celle-ci :

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

Notez que nous utilisons une API déclarative à base de propriétés, pour que cette classe puisse être facilement utilisée avec du QML ou du C++. La propriété rollingMeanFrameCount est accessible en lecture et écriture, comme la plupart des propriétés ; l'implémentation de ces fonctions de transfert et de récupération est très facile. Cette propriété sera utilisée pour contrôler le nombre de rafraîchissements durant lesquels nous calculons la moyenne du nombre d'images par seconde. La propriété framePerSecond est une propriété en lecture seule : sa valeur sera définie depuis la tâche que nous aurons écrite dans le composant FpsMonitor depuis le groupe de fils d'exécution de Qt 3D.

IV - Création d'une petite application de test

Avant que nous puissions utiliser notre composant en QML, nous devons enregistrer son type avec le système de types de QML. C'est également très simple, il suffit d'une ligne pour déclarer son type.

main.cpp

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

Nous en profitons aussi pour exporter la fenêtre dans le contexte QML (nous en aurons besoin dans peu de temps).

Une fois que cela est fait, nous pouvons importer le module QML CustomModule et utiliser le composant FpsMonitor comme nous le ferions pour un des types intégrés.

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

Bien sûr, à ce moment-là, FpsMonitor ne fait pas grand-chose, si ce n'est paramétrer la valeur de la propriété rollingMeanFrameCount. Une fois que la tâche en arrière-plan aura terminé son exécution, ce code mettra à jour le titre de la fenêtre pour afficher la moyenne du nombre d'images par seconde à chaque rafraîchissement et le nombre d'images utilisées pour le calculer.

Dans la prochaine partie, nous implémenterons le nœud de calcul correspondant à FpsMonitor et nous nous assurerons qu'il reçoit les demandes de création et de suppression. De plus, nous mettrons en place les communications entre le premier plan et les calculs.

VI - Remerciements

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à KDAB pour nous avoir autorisés à traduire cet article !

Je tiens à remercier Thibaut Cuvelier et Alexandre Laurent pour leurs conseils et relectures, ainsi que Claude Leloup pour ses corrections express et Maxy35 pour sa seconde passe.