Parler dans le cadre d'un jeu basé sur le rendu openGL:
Supposons qu'il existe deux fils:
Met à jour la logique et la physique du jeu, etc. pour les objets du jeu
Effectue des appels de dessin openGL pour chaque objet de jeu en fonction des données dans les objets de jeu (ce thread 1 continue de se mettre à jour)
À moins que vous ayez deux copies de chaque objet de jeu dans l'état actuel du jeu, vous devrez mettre en pause Thread 1 pendant que Thread 2 effectue les appels de tirage sinon les objets de jeu seront mis à jour au milieu d'un appel de tirage pour cet objet, ce qui est indésirable!
Mais arrêter le thread 1 pour effectuer des appels en toute sécurité depuis le thread 2 tue tout le but du multithreading / simultané
Existe-t-il une meilleure approche pour cela autre que l'utilisation de centaines ou de milliers ou la synchronisation d'objets / clôtures afin que l'architecture multicœur puisse être exploitée pour les performances?
Je sais que je peux toujours utiliser le multithreading pour charger des textures et compiler des shaders pour les objets qui ne font pas encore partie de l'état actuel du jeu, mais comment le faire pour les objets actifs / visibles sans provoquer de conflit avec le dessin et la mise à jour?
Que se passe-t-il si j'utilise un verrouillage de synchronisation distinct dans chacun des objets du jeu? De cette façon, n'importe quel thread ne bloquerait qu'un seul objet (cas idéal) plutôt que pour tout le cycle de mise à jour / dessin! Mais combien coûte la prise de verrous sur chaque objet (le jeu peut avoir mille objets)?
la source
Réponses:
L'approche que vous avez décrite, en utilisant des verrous, serait très inefficace et probablement plus lente que l'utilisation d'un seul thread. L'autre approche consistant à conserver des copies de données dans chaque thread fonctionnerait probablement bien "en termes de vitesse", mais avec un coût de mémoire prohibitif et une complexité de code pour garder les copies synchronisées.
Il existe plusieurs approches alternatives à cela, une solution populaire pour le rendu multi-thread est d'utiliser un double tampon de commandes. Cela consiste à exécuter le backend du moteur de rendu dans un thread séparé, où tous les appels de dessin et la communication avec l'API de rendu sont effectués. Le thread frontal qui exécute la logique du jeu communique avec le moteur de rendu principal via un tampon de commande (à double tampon). Avec cette configuration, vous n'avez qu'un seul point de synchronisation à la fin d'une trame. Alors que le front-end remplit un tampon avec des commandes de rendu, le back-end consomme l'autre. Si les deux fils sont bien équilibrés, aucun ne doit mourir de faim. Cette approche n'est cependant pas optimale, car elle introduit une latence dans les images rendues, de plus, le pilote OpenGL est susceptible de le faire déjà dans son propre processus, de sorte que les gains de performances devraient être soigneusement mesurés. Il n'utilise également que deux cœurs, au mieux. Cette approche a cependant été utilisée dans plusieurs jeux à succès, tels que Doom 3 et Quake 3.
Les approches plus évolutives qui font un meilleur usage des processeurs multicœurs sont celles basées sur des tâches indépendantes , où vous déclenchez une demande asynchrone qui est traitée dans un thread secondaire, tandis que le thread qui a déclenché la demande continue avec d'autres travaux. La tâche devrait idéalement ne pas avoir de dépendances avec les autres threads, pour éviter les verrous (évitez également les données partagées / globales comme la peste!). Les architectures basées sur des tâches sont plus utilisables dans des parties localisées d'un jeu, telles que les animations informatiques, la recherche d'IA, la génération de procédures, le chargement dynamique des accessoires de scène, etc. Les jeux sont naturellement riches en événements, la plupart des types d'événements sont asynchrones, de sorte qu'il est facile à faire fonctionner dans des threads séparés.
Enfin, je recommande la lecture:
Bases du filetage pour les jeux par Intel.
Concurrence efficace par Herb Sutter (plusieurs liens vers d'autres bonnes ressources dans cette page).
la source