Comment résoudre les gros besoins en mémoire vidéo dans un jeu en 2D?
Nous développons actuellement un jeu 2D (Factorio) en allegro C / C ++, et nous rencontrons un problème d'augmentation des besoins en mémoire vidéo à mesure que le contenu du jeu augmente.
Nous rassemblons actuellement toutes les informations sur les images qui vont être utilisées en premier, rognons toutes ces images autant que possible et les organisons dans de grands atlas aussi étroitement que possible. Ces atlas sont stockés dans la mémoire vidéo, dont la taille dépend des limites du système. Actuellement, il s'agit généralement de 2 images de 8192x8192, elles nécessitent donc une mémoire vidéo de 256 Mo à 512 Mo.
Ce système fonctionne assez bien pour nous, car avec certaines optimisations personnalisées et la division du fil de rendu et de mise à jour, nous sommes en mesure de dessiner des dizaines de milliers d'images sur l'écran en 60 ips; nous avons beaucoup d’objets sur l’écran et il est essentiel de permettre un zoom arrière important. Au fur et à mesure que nous souhaiterions en ajouter davantage, les exigences en matière de mémoire vidéo vont poser problème, de sorte que ce système ne peut pas tenir.
Une des choses que nous voulions essayer est d’avoir un atlas avec les images les plus courantes, et le second comme cache. Les images y seraient déplacées de la mémoire bitmap, à la demande. Cette approche pose deux problèmes:
- Le dessin de bitmap mémoire en bitmap vidéo est extrêmement lent, en mode allégro.
- Il n'est pas possible de travailler avec une image bitmap vidéo autrement que dans le fil principal, dans allegro, elle est donc pratiquement inutilisable.
Voici quelques exigences supplémentaires que nous avons:
- Le jeu doit être déterministe, de sorte que les problèmes de performances / temps de chargement ne peuvent jamais modifier l’état du jeu.
- Le jeu est en temps réel et sera bientôt multijoueur également. Nous devons éviter le moindre bégaiement à tout prix.
- La plupart du jeu est un monde ouvert continu.
Le test consistait à tirer 10 000 sprites dans un lot pour des tailles de 1x1 à 300x300, plusieurs fois pour chaque configuration. J'ai fait les tests sur la Nvidia Geforce GTX 760.
- Le dessin d'une image à une autre prend 0,1 us par image-objet, lorsque l'image source ne change pas entre les images individuelles (variante de l'atlas); la taille n'avait pas d'importance
- Dessin de bitmap vidéo en bitmap vidéo, alors que le bitmap source était commuté entre les dessins (variante non atlas), prenait 0,56 us par sprite; la taille n'avait pas d'importance non plus.
- La mémoire bitmap au dessin vidéo bitmap était vraiment suspecte. Les tailles de 1x1 à 200x200 prenaient 0.3us par bitmap, donc pas si lent. Pour les plus grandes tailles, le temps a commencé à augmenter de manière très spectaculaire, à 9US pour 201x201 à 3116US pour 291x291.
Utiliser atlas augmente les performances d'un facteur supérieur à 5. Si j'avais 10 ms pour le rendu, avec un atlas, je suis limité à 100 000 sprites par image, et sans cela, une limite de 20 000 sprites. Ce serait problématique.
J'essayais également de trouver un moyen de tester la compression bitmap et le format bitmap 1bpp pour les ombres, mais j'étais incapable de trouver un moyen de le faire avec allegro.
la source
Réponses:
Nous avons un cas similaire avec notre RTS (KaM Remake). Toutes les unités et maisons sont des sprites. Nous avons 18 000 sprites pour les unités et les maisons et le terrain, plus environ 6 000 autres pour les couleurs de l’équipe (appliquées en tant que masques). Longtemps étendus, nous avons également environ 30 000 caractères utilisés dans les polices.
Il existe donc quelques optimisations par rapport aux atlas RGBA32 que vous utilisez:
Séparez d’abord votre pool d’images-objets en plusieurs petits atlas et utilisez-les à la demande, comme indiqué dans d’autres réponses. Cela permet également d’utiliser différentes techniques d’optimisation pour chaque atlas individuellement . Je suppose que vous aurez un peu moins de RAM gaspillée, car lors de l’emballage de textures aussi énormes, il y a généralement des zones inutilisées en bas;
Essayez d’utiliser des textures palettisées . Si vous utilisez des shaders, vous pouvez "appliquer" la palette dans le code des shaders;
Vous pourriez envisager d'ajouter une option permettant d'utiliser RGB5_A1 au lieu de RGBA8 (si, par exemple, les ombres en damier conviennent à votre jeu). Évitez autant que possible l'alpha 8 bits et utilisez RGB5_A1 ou des formats équivalents avec une précision moindre (comme RGBA4), ils occupent deux fois moins d'espace;
Assurez-vous de bien tasser les images-objets dans des atlas (reportez-vous à la section Algorithmes d'emballage dans les bacs), faites-les pivoter si nécessaire et voyez si vous pouvez superposer des angles transparents aux images-objets losange;
Vous pouvez essayer des formats de compression matérielle (DXT, S3TC, etc.) - ils peuvent réduire considérablement l'utilisation de la RAM, mais vérifiez les artefacts de compression - sur certaines images, la différence peut être imperceptible (vous pouvez l'utiliser de manière sélective, comme décrit dans la première puce), mais sur certains - très prononcé. Différents formats de compression entraînent différents artefacts. Vous pouvez donc choisir celui qui convient le mieux à votre style.
Examinez le fractionnement de gros sprites (bien sûr, pas manuellement, mais dans l’intégrateur d’atlas de texture) en sprite d’arrière-plan statique et en sprites plus petits pour des pièces animées.
la source
Tout d’abord, vous devez utiliser plus d’atlas de texture plus petits. Moins vous avez de textures, plus la gestion de la mémoire sera difficile et rigide. Je suggérerais une taille d'atlas de 1024, auquel cas vous auriez 128 textures au lieu de 2 ou 2048, auquel cas vous auriez 32 textures, que vous pourriez charger et décharger au besoin.
La plupart des jeux font cette gestion des ressources en ayant des limites de niveau, tandis qu'un écran de chargement affiche toutes les ressources qui ne sont plus nécessaires dans le niveau suivant sont déchargées et les ressources nécessaires sont chargées.
Une autre option est le chargement à la demande, qui devient nécessaire si les limites de niveau ne sont pas souhaitées ou même si un seul niveau est trop grand pour tenir en mémoire. Dans ce cas, le jeu tentera de prédire ce que le joueur verra dans le futur et de le charger en arrière-plan. (Par exemple: tout ce qui se trouve actuellement à 2 écrans du lecteur.) En même temps, les choses qui ne sont plus utilisées depuis longtemps seront déchargées.
Il y a cependant un problème: que se passe-t-il lorsque quelque chose d'imprévu se produit que le jeu n'a pas pu prévoir?
la source
Wow, c’est une quantité considérable de sprites d’animation, générés à partir de modèles 3D, je suppose?
Vous ne devriez vraiment pas créer ce jeu en 2D brut. Lorsque vous avez une perspective fixe, il se passe quelque chose d'amusant, vous pouvez mélanger de manière transparente des sprites et des arrière-plans pré-rendus avec des modèles 3D rendus en direct, qui ont été très utilisés par certains jeux. Si vous voulez de telles animations fines, cela semble être le moyen le plus naturel de le faire. Obtenez un moteur 3D, configurez-le pour utiliser une perspective isométrique et restituez les objets pour lesquels vous continuez à utiliser des images-objets sous la forme de simples surfaces planes avec une image. Et vous pouvez utiliser la compression de texture avec un moteur 3D, ce qui constitue à lui seul un grand pas en avant.
Je ne pense pas que le chargement et le déchargement vous apporteront beaucoup, car vous pouvez avoir pratiquement tout à l'écran en même temps.
la source
Premièrement, trouvez le format de texture le plus efficace possible tout en étant satisfait des visuels du jeu, qu'il s'agisse d'une compression RGBA4444, DXT, etc. Si vous n'êtes pas satisfait des artefacts générés dans une image compressée en alpha DXT, serait-il viable? rendre les images non transparentes à l'aide de la compression DXT1 pour la couleur combinée à une texture de masquage en niveaux de gris de 4 ou 8 bits pour l'alpha? J'imagine que vous resteriez sur RGBA8888 pour l'interface graphique.
Je préconise de diviser les choses en textures plus petites en utilisant le format que vous avez choisi. Déterminez les éléments qui sont toujours à l'écran et donc toujours chargés, il peut s'agir des atlas de terrain et d'interface graphique. Je diviserais ensuite autant que possible les éléments restants qui sont généralement rendus ensemble. Je n'imagine pas que vous perdriez trop de performances même en allant jusqu'à 50-100 appels sur PC, mais corrigez-moi si je me trompe.
La prochaine étape consistera à générer les versions mipmap de ces textures, comme l’a souligné une personne. Je ne les stockerais pas dans un seul fichier mais séparément. Donc, vous vous retrouveriez avec les versions 1024x1024, 512x512, 256x256, etc. de chaque fichier et je le ferais jusqu'à ce que j'atteigne le niveau de détail le plus bas que je souhaiterais jamais afficher.
Maintenant que vous avez les textures séparées, vous pouvez créer un système de niveau de détail (LOD) qui charge les textures pour le niveau de zoom actuel et les décharge si elles ne sont pas utilisées. Une texture n'est pas utilisée si l'élément en cours de rendu n'est pas à l'écran ou n'est pas requis par le niveau de zoom actuel. Essayez de charger les textures dans la RAM vidéo dans un fil distinct des threads de mise à jour / rendu. Vous pouvez afficher la texture de niveau de détail la plus basse jusqu'à ce que la texture requise soit chargée. Cela peut parfois entraîner un basculement visible entre une texture peu détaillée / très détaillée, mais j'imagine que cela ne se produit que lorsque vous effectuez un zoom avant et arrière extrêmement rapide lorsque vous vous déplacez sur la carte. Vous pouvez rendre le système intelligent en essayant de précharger où vous pensez que la personne se déplacera ou zoomera et chargera autant que possible dans les limites de la mémoire actuelle.
C'est le genre de chose que je testerais pour voir si cela aide. J'imagine que pour obtenir des niveaux de zoom extrêmes, vous aurez inévitablement besoin d'un système LOD.
la source
Je pense que la meilleure approche consiste à scinder la texture dans de nombreux fichiers et à les charger à la demande. Votre problème est probablement que vous essayez de charger des textures plus volumineuses dont vous auriez besoin pour une scène 3D complète et que vous utilisez Allegro pour cela.
Pour le grand zoom que vous souhaitez pouvoir appliquer, vous devez utiliser des mipmaps. Les mipmaps sont des versions de résolution inférieure de vos textures qui sont utilisées lorsque les objets sont suffisamment éloignés de la caméra. Cela signifie que vous pouvez enregistrer votre 8192x8192 au format 4096x4096, puis un autre format 2048x2048, etc., et que vous basculez vers les résolutions inférieures à mesure que l’image-objet apparaît à l’écran. Vous pouvez à la fois les enregistrer sous forme de textures distinctes ou les redimensionner lors du chargement (mais la création de mipmaps pendant l'exécution augmentera les temps de chargement de votre jeu).
Un système de gestion approprié chargerait les fichiers requis à la demande et libérerait les ressources lorsque personne ne les utilisait, entre autres choses. La gestion des ressources est un sujet important dans le développement de jeux et vous réduisez votre gestion à une simple cartographie de coordonnées en une seule texture, ce qui est proche de ne pas avoir de gestion du tout.
la source
Je recommande de créer davantage de fichiers atlas pouvant être compressés avec zlib et sortis de la compression pour chaque atlas. En disposant de plus d’atlas et de fichiers de taille plus petite, vous pouvez limiter la quantité de données d’images actives dans la mémoire vidéo. En outre, implémentez le mécanisme de triple tampon afin de préparer chaque cadre de dessin plus tôt et d’avoir une chance de terminer plus rapidement, de sorte que les balbutiants ne s’affichent pas à l’écran.
la source