Bonjour ! Vous avez été nombreux à partager notre billet de blog sur l'adaptation d'Outer Wonders pour Linux, ouvrant ainsi des discussions et des débats passionnés, dans lesquels l'expérience de développement de jeux pour Linux était souvent comparée à l'expérience de développement de jeux pour Windows. Nous vous proposons donc aujourd'hui un retour d'expérience sur le développement de la démo itch.io d'Outer Wonders pour Windows !
Tout comme notre billet de blog sur l'adaptation d'Outer Wonders pour Linux, nous avons essayé de simplifier ce billet de blog autant que possible, mais quelques connaissances techniques seront tout de même requises pour saisir l'intégralité du billet de blog.
Fragmentation et compatibilité
Une fragmentation accumulée sur des décennies
Tout comme pour Linux, nous ne pouvons pas vraiment considérer Windows comme une seule et unique plate-forme. L'existence de multiples versions de Windows nous amène à prendre un compte un paramètre essentiel : la rétrocompatibilité. Autrement dit : un jeu créé par un développeur utilisant Windows 10 fonctionnera-t-il sur un ordinateur sous Windows 8 ? Ou sous Windows 7 ? Jusqu'à quelle version de Windows le développeur peut-il réellement remonter tout en permettant à son jeu de rester fonctionnel ?
Pour mieux aborder cette problématique, il convient, au moment d'utiliser des fonctionnalités de Windows, de bien prendre garde à la version minimale requise de Windows pour la fonctionnalité en question.
À titre d'exemple, les applications classiques (celles téléchargées en dehors du Windows Store), aussi appelées applications Win32, reposent toutes sur l'idée d'ouvrir une fenêtre dans laquelle se trouveront un certain nombre d'éléments interactifs. L'ouverture de fenêtres est rendue possible par un ensemble de fonctionnalités nommées CreateWindowA()
, CreateWindowW()
, CreateWindowExA()
, et CreateWindowExW()
. Ces fonctionnalités sont maintenues dans Windows depuis plus de 20 ans (certaines ont même près de 30 ans).
Une difficile vérité nous apparaît alors : une même possibilité a priori basique (l'ouverture de fenêtres) est proposée aux développeurs sous plusieurs formes différentes (4 ici), ce qui complexifie grandement le développement d'applications. Et l'ouverture de fenêtres n'en est pas le seul exemple : la récupération d'informations sur la version de Windows de l'utilisateur est proposée aux développeurs via 3 fonctionnalités distinctes nommées GetVersion()
, GetVersionExA()
, et GetVersionExW()
.
Cette fragmentation existe pour des raisons historiques : certaines fonctionnalités sont par exemple déclinées en une version dont le nom se finit par A
, et une autre version dont le nom se finit par W
. La première version n'accepte alors que du texte ANSI (autrement dit : uniquement des caractères latins), et la seconde version n'accepte que du texte basé sur UTF-16 (autrement dit : beaucoup plus de caractères sont autorisés, et le texte écrit par exemple en chinois ou en arabe est utilisable).
Cela est dû à une époque où l'informatique n'était pas assez mature pour permettre l'utilisation de caractères non-latins, et où seul un équivalent de l'actuelle version en A
de chaque fonctionnalité existait. L'exportation de l'informatique vers des pays en dehors de l'Occident a créé un besoin de mieux prendre en charge les caractères autres que latins, contraignant ainsi les créateurs de Windows à ajouter une version en W
de chaque fonction, qui serait alors plus flexible que la version en A
. Après tout, les utilisateurs japonais, par exemple, aimeraient certainement pouvoir avoir des fenêtres dont le titre serait écrit en caractères japonais !
Une question que nous pourrions nous poser est : pourquoi avoir ajouté une nouvelle version de chaque fonctionnalité plutôt que de remplacer ou améliorer l'existante ? La réponse se tient en un mot : la rétrocompatibilité. La philosophie mise en œuvre par Microsoft consiste à considérer que les utilisateurs doivent pouvoir continuer à profiter d'anciennes applications même sur des versions récentes sur Windows. Les anciennes fonctionnalités, aussi limitées soient-elles, devaient donc être intégralement conservées. C'est ainsi que de nombreux jeux créés dans les années 2000 restent jouables sur des versions récentes de Windows.
Si cela est pratique pour les utilisateurs, il est en complètement autrement pour les développeurs, qui doivent alors choisir entre exploiter des fonctionnalités modernes et complètes en perdant les utilisateurs dont le système n'est pas à jour, ou bien se contenter de fonctionnalités plus limitées en gagnant des utilisateurs qui n'ont alors pas besoin de mettre à jour leur système.
Heureusement, des outils ont depuis été créés pour que les développeurs aient moins souvent à prendre de telles décisions.
Utilisation de SDL2 pour des fonctionnalités simplifiées
Nous vous parlions déjà de l'intérêt des couches d'abstraction telles que SDL2 dans notre article sur l'adaptation d'Outer Wonders de façon multi-plates-formes ou encore notre billet de blog sur l'adaptation d'Outer Wonders sur Linux, et nous confirmons dans ce billet de blog leur intérêt dans le cas de Windows.
En effet, au début de ce billet de blog, nous avons précisé que Windows ne pouvait pas réellement être considéré comme une plate-forme unique, mais plusieurs plates-formes. Dans ce contexte, SDL2 joue à nouveau son rôle à merveille, puisqu'il est capable de déterminer automatiquement de quelles fonctionnalités le système Windows de l'utilisateur dispose, et de faire appel aux fonctionnalités les plus récentes suivant le système de l'utilisateur. Grâce à un travail considérable réalisé en continu sur plusieurs décennies et de nombreuses générations de Windows, SDL2 permet donc de traiter les différentes plates-formes que sont les différentes versions de Windows comme une seule et même plate-forme.
C'est pourquoi, même pour les développeurs souhaitant créer un jeu basé sur un moteur maison ne ciblant que Windows, nous ne saurions trop recommander l'utilisation de SDL2 ou un outil équivalent en lieu et place d'une utilisation directe des fonctionnalités de Windows.
Dans de rares cas, il faut encore plus
Il existe de rares fonctionnalités propres à chaque plate-forme qui ne sont pas encore entièrement prises en charge par des couches d'abstraction comme SDL2.
La détection des langues de l'utilisateur en est une. Même si une prise en charge initiale de cette fonctionnalité existe depuis la version 2.0.14 de SDL, elle n'est pas encore listée dans la documentation à destination des développeurs.
A priori, la récupération des langues de l'utilisateur paraît simple. Il s'agit d'obtenir la liste des langues affichée dans l'interface suivante, accessible depuis les paramètres de Windows :
Une recherche rapide dans la documentation de Windows nous permet de trouver la fonctionnalité GetUserPreferredUILanguages()
, qui permet d'obtenir les langues préférées de l'utilisateur.
Nous avons été surpris en constatant les résultats obtenus par le biais de cette fonctionnalité. Pour l'exemple montré ci-dessus, cette fonctionnalité n'a renseigné que le français de France et l'anglais des États-Unis comme langues préférées de l'utilisateur. Le japonais et le coréen étaient ignorés. Nos expériences nous ont même amenés à conclure que cette fonctionnalité introduite avec Windows Vista part du principe que l'utilisateur a pour langues préférées la langue actuellement utilisée par son système, suivie de l'anglais des États-Unis.
De longues recherches nous ont permis de révéler l'existence d'une fonctionnalité très méconnue introduite avec Windows 8, nommée (attention !) Windows.System.UserProfile.IGlobalizationPreferencesStatics::Languages()
. En la testant, toujours sur l'exemple ci-dessus, cette fonctionnalité a bien été en mesure d'indiquer que les langues préférées de l'utilisateur étaient, dans cet ordre, le français de France (fr-FR
), le japonais (ja
), l'anglais des États-Unis (en-US
) et le coréen (ko
).
À notre connaissance, peu d'outils ou d'applications font appel à cette fonctionnalité. Par exemple, SDL2 ne le fait pas, mais ceci pourrait faire l'objet d'une amélioration de SDL2.
Le cas complexe de DirectX…
…ou, plutôt, de Direct3D, à savoir la technologie de rendu 3D intégrée à Windows.
Pourquoi nous avons opté pour Direct3D 11 par défaut pour Outer Wonders
Pourquoi évoquer Direct3D ? En réalité, d'autres technologies similaires comme Vulkan et OpenGL (ainsi que sa variante OpenGL ES) existent, et sont compatibles avec davantage de plates-formes telles que Linux et ses dérivés comme Android.
Cependant, dans notre experience, la prise en charge d'OpenGL sur Windows est de qualité très variable. Sur plusieurs de nos machines, l'utilisation d'OpenGL 3.3 aboutit à une utilisation à 100 % d'un cœur de microprocesseur (ce qui est plutôt inattendu et particulièrement énergivore) et des performances qui, bien que correctes, sont inférieures d'environ 40 % par rapport à ce que propose Vulkan 1.0 ou Direct3D 11.
Concernant Vulkan, même si une large majorité des machines le prennent en charge aujourd'hui, il existe encore une proportion non-négligeable de joueurs qui n'en bénéficient pas encore. À l'écriture de ces lignes, sachant que plus de 99 % des machines Windows prenant en charge Direct3D 12 prennent en charge Vulkan, environ 8 % des joueurs Windows ne bénéficient pas de Direct3D 12 (et donc de Vulkan) d'après l'enquête de Steam sur le matériel et le logiciel, et environ 42 % des utilisateurs d'Android ne bénéficient pas de Vulkan.
En tant que développeur, il peut être tentant malgré tout de décider d'adopter la dernière technologie, à savoir Vulkan ou bien Direct3D 12. Cependant, ces deux technologies sont particulièrement complexes, et demandent énormément de travail manuel aux développeurs comparé à ce que les précédentes versions de Direct3D, ou bien OpenGL, ne nécessitaient. De plus, nous avons constaté des bugs très importants dans la mise en œuvre de Vulkan sur certaines cartes graphiques.
Étant donnés ces paramètres, nous avons décidé, après avoir testé OpenGL 3.3 et Vulkan 1.0, de proposer un rendu Direct3D 11 par défaut pour les utilisateurs Windows (même si des modes de rendu alternatifs via OpenGL 3.3 et Vulkan 1.0 sont également disponibles) pour Outer Wonders.
Compatibilité maximale avec d'anciennes versions de Windows
Un autre argument en faveur de Direct3D 11 est que Direct3D 11 est compatible avec de très nombreuses versions de Windows, allant de Windows 10 à Windows… Vista. Ce qui n'est pas nécessairement le cas de Vulkan ou même OpenGL, dont la version prise en charge dépend du pilote de votre carte graphique.
Cependant, l'utilisation de Direct3D 11 dans un jeu ne garantit pas nécessairement au développeur que son jeu sera compatible avec d'anciennes versions de Windows. Il convient de faire attention à un détail supplémentaire : le niveau de fonctionnalité pris en charge par la machine de l'utilisateur.
L'utilisation de Direct3D 11 se base sur l'une des fonctionnalités nommées D3D11CreateDevice
et D3D11CreateDeviceAndSwapChain
, dont le choix est laissé au développeur du jeu. Par défaut, ces 2 fonctionnalités cherchent à exploiter pleinement les fonctionnalités proposées par Direct3D 11, se rabattant sur les fonctionnalités de Direct3D 10 ou Direct3D 9 si la carte graphique ne prend pas en charge les fonctionnalités de Direct3D 11.
Par conséquent, quand un jeu utilise Direct3D 11, les fonctionnalités réellement utilisées sont peut-être plutôt celles de Direct3D 10 ou Direct3D 9, mais utilisées sous les traits de Direct3D 11. C'est ce que Microsoft désigne comme étant le niveau de fonctionnalité de Direct3D (Direct3D feature level). Ainsi, lorsque la configuration minimale ou recommandée d'un jeu indique DirectX: Version 10, il est possible que le jeu fasse appel à Direct3D 11, mais requière que le niveau de fonctionnalité pris en charge par votre carte graphique soit le niveau correspondant à Direct3D 10.
Pour obtenir une compatibilité maximale, le développeur doit donc s'assurer que le niveau de fonctionnalité de Direct3D requis par son jeu soit le plus bas possible (le plus bas étant le niveau de fonctionnalité 9.1, correspondant à Direct3D 9.0a).
C'est par le biais de tests sur des machines anciennes que nous avons découvert ces éléments de fonctionnement. En effet, sur une machine datée de 2007 fonctionnant sous Windows Vista, nous avons constaté plusieurs messages d'erreur inattendus. C'est alors que nous avons constaté que le niveau de fonctionnalité pris en charge par la carte graphique de cet ordinateur était 9.3 (le niveau de fonctionnalité correspondant à Direct3D 9.0c). Notre utilisation de Direct3D 11 n'était donc pas compatible avec ce niveau de fonctionnalité, mais les correctifs à apporter se sont avérés très simples, se résumant au changement de quelques paramètres dans l'utilisation de Direct3D 11.
Cependant, les développeurs n'ont pas nécessairement besoin de recourir à des machines anciennes pour corriger les éventuelles erreurs de compatibilité avec Direct3D. Windows permet en effet d'émuler tout niveau de fonctionnalité de Direct3D inférieur à celui pris en charge par la machine du développeur. Les fonctionnalités D3D11CreateDevice
et D3D11CreateDeviceAndSwapChain
permettent en effet de spécifier quel niveau de fonctionnalité de Direct3D est demandé par l'application. Il suffit alors pour le développeur de spécifier un niveau de fonctionnalité faible, et les erreurs qui ne se produisaient que sur de vieilles machines peuvent alors être reproduites sur leur machine plus récente, rendant ces erreurs plus faciles à corriger !
Des tests stricts, à nouveau
Tout comme pour Linux, rien ne vaut des tests sur de nombreuses machines pour s'assurer du bon fonctionnement du jeu sous de multiples versions de Windows.
À nouveau, il s'agit tout simplement de s'assurer en premier lieu que l'application démarre correctement.
Il peut notamment arriver que des erreurs telles que :
"Impossible d'exécuter le code, car msvcr140.dll est introuvable. La réinstallation du programme peut corriger ce problème."
fassent leur apparition sur certaines machines.
Ceci est lié au fait que le jeu ait été produit avec Visual C++ (les fichiers msvcrXXX.dll
ou msvcpXXX.dll
sont en effet des fichiers liés à Visual C++ qui doivent être installés en même temps que toute application produite avec Visual C++, et correspondent à l'équivalent, pour Windows, de l'indispensable dépendance glibc
de Linux dont nous avons parlé ici). 2 solutions s'offrent alors au développeur :
- Fournir un installeur de dépendances Visual C++ avec l'application, correspondant à la version de Visual C++ utilisée par le développeur ;
- Produire le jeu plutôt avec un autre outil, tel que GCC ou Clang, ce qui rendra le jeu indépendant des fichiers de Visual C++ (fichiers
msvcrXXX.dll
etmsvcpXXX.dll
).
Nous avons opté pour la seconde solution (ou, plus précisément, comme Outer Wonders est basé sur Rust, en utilisant les cibles nommées x86_64-pc-windows-gnu
ou i686-pc-windows-gnu
), qui épargne au joueur une étape supplémentaire dans l'installation du jeu, et rend Outer Wonders entièrement portable !
Constatez par vous-même, via la capture d'écran suivante issue de ce billet de blog, Outer Wonders fonctionnant sur Windows Vista.
Ainsi s'achève ce billet de blog technique ! Si vous ne l'avez pas encore fait, n'hésitez pas à vous essayer à la démo d'Outer Wonders sur itch.io ! Rejoignez notre serveur Discord, suivez-nous sur Twitter, Facebook et Instagram pour suivre nos actualités et avoir une nouvelle énigme tous les mercredis ! Suivez aussi notre flux RSS pour être tenus au courant de la publication de nouveaux billets de blog.
À bientôt !