Table of Contents

Commit 2017-01-06

Ce commit consiste à modifier le code source de Metafor (et keygen) pour qu'il soit compilable avec VTK 6 et Qt 5. C'est une des dernières étapes pour pouvoir utiliser le Visual Studio 2015.

Le code reste cependant compatible avec VTK 5 et Qt 4. Les batteries sont d'ailleurs passées avec les anciennes versions de ces libs. Le code python (vtk/PyQt) n'a pas encore été traduit. Il n'est donc pas encore possible de passer les batteries avec VTK 6 (à cause de geniso et biomec qui utilisent vtk). Enfin, Luc n'a pas encore intégré ces 2 nouyelles versions dans ses libs.

Portage VTK 6

Contexte

Continuer à utiliser VTK 5 devient de plus en plus difficile avec les OS et les compilateurs qui évoluent ainsi que la “nouvelle” norme C++ qui nous fait de l'oeil. En effet,

Essai #1: Portage brutal

Malheureusement VTK 6 n'est pas tout à fait compatible avec VTK 5. Le pipeline VTK distingue maintenant les connexions entre “données et filtres” et les connexions entre “ports de filtres”. Par exemple, si dataset est un vtkDataSet (un maillage de type vtkPolyData ou vtkUnstructuredGrid) et qu'on veut filtrer ce dataset avec filter, on pouvait écrire avec VTK 5:

filter->SetInput(dataset);

Il faudrait maintenant écrire ceci avec VTK 6:

filter->SetInputData(dataset);

En effet, dataset est un ensemble de données et pas un filtre. La fonction setInput() n'existe plus! Mais ce n'est pas tout: la connexion qui, sous VTK 5, était souvent (pas toujours) relativement intelligente (le filtre testait régulièrement si dataset était modifié pour remouliner les données lorsque le mapper s'exécutait), ne l'est plus jamais.

Un portage brutal VTK 5 vers VTK 6 consisterait à écrire:

#if (VTK_MAJOR_VERSION <= 5)
    filter->SetInput(dataset);
#else
    filter->SetInputData(dataset);
    filter->Update();               // on force la mise à jour!
#endif

C'est la première méthode que j'ai utilisée… Ca marche… mais j'ai fait mieux finalement.

Les connexions de type Filtre⇔Filtre pouvaient s'écrire sous VTK 5:

filtre2->SetInput(filtre1->GetOutput());

Ce type de connexion était encore un fois relativement intelligent dans VTK 5. Ça ne marche plus dans VTK 6: il faut faire:

filtre2->SetInputConnection(filtre1->GetOutputPort());

En d'autres mots, on ne connecte plus les données des 2 filtres mais plutôt leur “ports” (de type vtkAlgorithmOutput, quel que soit le type de dataset qui transite à travers la connexion). Par contre, bonne nouvelle, dans ce cas-ci, cette écriture est compatible avec VTK 5 et elle est toujours intelligente (filtre2 teste toujours si l'output de filtre1 est à jour avant de s'exécuter à la demande du mapper en aval).

Même genre de problème d'écriture pour les mappers. Il était possible de faire un SetInput(data) qui peut être directement écrit sous forme d'un setInputConnection(port) (du moins si l'entrée est bien un filtre… ce qui n'est pas toujours le cas).

On a aussi le problème avec les “sources VTK” où le SetSource(data) peut être remplacé par un SetSourceConnection(port) dans le cas où l'entrée est bien un filtre.

Dans la visu de Metafor, il y a beaucoup de filtres et certaines classes ont été écrites pour rassembler certaines opérations assez complexes. Par exemple, l'objet Slices crée une série de tranches à partir d'un maillage 3D. Il contient tout ce qu'il faut pour construire un maillage à partir d'un autre (des objets filtres, un mapper et un actor). Dans cet exemple, l'entrée de Slices est donc un vtkUnstructuredGrid. Cependant, dans Metafor, ce maillage d'entrée est soit le maillage initial, soit un maillage qui sort d'un filtre et qui a déjà subi des opérations de duplication pour tenir compte des symétries X, Y, Z. Dans le cas de l'ancienne écriture VTK 5, on faisait un setInput() du vtkUnstructuredGrid sortant de l'éventuel filtre de symétrie. Pour VTK 6, on voit bien qu'on a deux cas différents (avec ou sans symétrie) qui vont se traduire par des appels différents, c'est-à-dire des if() et des Update() additionnels dans le code (plus des #ifdef/#endif liés au changement d'API).

Je n'aimais pas du tout le résultat obtenu même s'il était fonctionnel et j'ai donc décidé de faire les choses plus proprement…

Essai #2: Nettoyage du pipeline VTK

Après quelques heures de “portage brutal”, j'ai donc eu un code compilable avec VTK 5 et VTK 6. A l'exécution, certains filtres n'étaient plus actifs en version VTK 6 et j'ai pu forcer le bon fonctionnement de la visu en ajoutant des filter→Update() un peu partout. Néanmoins ça ne me plaisait pas beaucoup plus plusieurs raisons:

J'ai donc décidé de modifier le source pour qu'il compile avec VTK 5 et VTK 6 avec un minimum de directives de préprocesseur #ifdef / #endif.

L'idée est donc d'éviter comme la peste les connections de type filter→SetInputData(dataset). Comme je l'ai déjà dit, ce type de connexion n'est pas dynamique et filter→Update() doit être appelé explicitement si le contenu du dataset change. VTK 6 propose une nouvelle méthode nommée SetInputDataObject() qui permet une connexion dynamique… mais cette fonction n'est pas disponible en VTK 5 et je voulais garder la compatibilité VTK 5 le plus longtemps possible. La solution m'est venue en regardant l'implémentation de SetInputDataObject() dans VTK 6: la méthode utilise le filtre vtkTrivialProducer qui est un filtre “vide” pour lequel on peut prescrire la sortie. Gros intérêt: la sortie est observée et les filtres connectés en aval s'exécutent automatiquement lorsque le dataset est modifié! On retrouve donc le comportement des vieilles versions de VTK en utilisant des commandes compatibles avec toutes les versions de VTK.

J'ai donc modifié tous les datasets de Metafor en leur ajoutant un vtkTrivialProducer et en connectant tous les filtres avec des SetInputConnection(). C'est une modification assez grosse puisque cela signifie qu'on ne manipule plus aucun pointeur vers des vtkPolyData ou vtkUnstructuredGrid mais plutôt vers des vtkAlgorithmOutput (des entrées/sorties de filtres).

J'ai traité la plupart des classes sauf:

Modification de la visu

Ayant dû lancer manuellement des tas de cas-tests pour déboguer mes modifs, je suis évidemment tombé sur des mini bugs ou des petits trucs à faire pour améliorer les choses dans la visu:

Nettoyage du code source

C'est une des choses qui m'a frappé quand j'ai modifié le code lors du “portage brutal”: il y a, dans le nouveau code, beaucoup de copier/coller et très peu de commentaires. J'ai décidé de démarrer une opération de nettoyage quand j'ai vu que les classes Elem0DDataSet, Elem1DDataSet, Elem2DDataSet étaient tout à fait IDENTIQUES! (depuis toujours).

Voila donc mes modifs:

Bugs

Je n'ai pas corrigé tous les bugs trouvés… et j'en ai même introduit 1 nouveau que je n'arrive pas à résoudre.

Anciens (ça plante dans la version officielle):

Nouveau: * Passer d'un affichage des champs scalaires en “texture mapping” à un affichage classique requiert 2 “updates”… Je n'ai pas compris pourquoi. Vu que, d'une part, personne n'utilise cette fonction et que, d'autre part, il suffit de cliquer 2x pour obtenir un affichage correct, je pense que ça ne mérite pas que je passe des jours là dessus.

Portage Qt 5

Un avantage d'utiliser VTK 6 est la compatibilité avec Qt 5 (nouvelle version de Qt disponible depuis 2012!). J'avais lu que le code Qt 4 est hautement compatible avec le code Qt 5 et qu'un manuel de portage existait. J'ai donc décidé de passer à Qt 5.

La dernière version (5.7.x) n'est plus compilable avec Visual Studio 2012. J'ai donc compilé sur mon PC la dernière version compatible, c'est-à-dire la 5.6.2. C'est déjà un énorme saut en avant.

Tout comme celle de VTK 6, la nouvelle architecture Qt 5 est beaucoup plus modulaire qu'auparavant. Il y a énormément de DLLs et un gros travail a été effectué pour permettre une sélection fine des composants utiles à l'application (ceci, j'imagine, pour rendre les exécutables les plus petits possible malgré l'augmentation des fonctionnalités en vue de l'utilisation sur GSM et tablettes).

En pratique, le code actuel a compilé quasiment du premier coup. Voici les modifs:

Au niveau de CMake, l'utilisation de Qt 5 nécessite une version 2.8.11 minimum. La compilation Qt 4 est toujours possible. J'ai ajouté un test qui vérifie que la version Qt utilisée par VTK et la version Qt trouvée sont bien identiques.

Le problème le plus ennuyeux que j'ai eu est lié à la présence de Qt 5 dans de nombreuses applications qui s'ajoutent dans le PATH système. Le risque de conflit est très grand et les symptômes sont assez bizarres (j'ai eu du mal à comprendre ce qui se passait alors que c'est pas la première fois qu'on a ce genre de problème.

Voila ce que donne une recherche de Qt5Gui.dll sur mon PC…

On voit qu'on peut avoir des problèmes avec Matlab, MikTeX, CMake, etc. Certains logiciels (Antidote p expl) ont pris la peine de renommer leurs libs avant de s'imposer dans le PATH mais ce n'est pas le cas des 3 programmes cités ci-dessus. Les autres programmes ne se mettent pas dans le PATH et ne posent donc pas de problème en pratique.

Comment voit-on qu'on a un conflit? On obtient ce genre d'erreur:

La solution est donc de s'assurer que les libs Qt5 fraîchement compilées et avec lesquelles on va linker Metafor sont devant les autres dans le PATH. Voilà un exemple qui pose problème:

La solution est d'intervertir la place des libs Qt5 et de CMake. cmake.exe marchera toujours puisqu'il utilisera en priorité les .dll qui sont à côté de l'exécutable.

D'une certaine manière on peut se réjouir de ces problèmes puisque cela signifie que Qt 5 est largement utilisé et qu'on ne risque donc pas de le voir disparaître du jour au lendemain. C'était donc un bon choix de bibliothèque graphique.

boman 2017/01/05 12:22