Comment résoudre les gros besoins en mémoire vidéo dans un jeu en 2D?

40

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:

  1. Le dessin de bitmap mémoire en bitmap vidéo est extrêmement lent, en mode allégro.
  2. 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.

Marwin
la source
1
Grand fan de ton jeu, j'ai soutenu la campagne Indiegogo. Je me gaver tous les quelques mois. Beau travail jusqu'à présent! J'ai supprimé les questions "quelle technologie utiliser" qui sont hors sujet pour le site. Les questions restantes restent assez larges, si vous avez quelque chose de plus spécifique, vous devriez essayer de réduire la portée.
MichaelHouse
Merci pour le soutien. Alors, où est le lieu de demander quelle technologie utiliser alors? Je ne cherche pas de réponse avec une recommandation de moteur spécifique, mais je ne pouvais pas trouver de comparaison approfondie des moteurs 2D et les inspecter manuellement un par un, y compris les tests de performance et de convivialité prendrait du temps.
Marwin
Consultez le bas de cette page pour certains endroits où vous pourrez poser des questions telles que "quelle technologie utiliser". Vous avez une question tout à fait valide et raisonnable, ce n'est tout simplement pas le type de question que nous traitons sur ce site. Même si vous ne recherchez pas un moteur spécifique, c'est vraiment le seul moyen de répondre à la question "Existe-t-il une technologie qui prend en charge X?". Quelqu'un pourrait simplement répondre «oui» et ne pas donner de recommandation, mais ce ne serait pas très utile. Bonne chance!
MichaelHouse
2
Êtes-vous en train de compresser vos textures?
GuyRT
3
@Marwin, les textures compressées peuvent donner de bien meilleurs résultats que les textures non compressées car elles réduisent la bande passante mémoire requise (ceci est particulièrement vrai sur les plates-formes mobiles où la bande passante est beaucoup plus basse). Vous pourriez économiser une énorme quantité de mémoire simplement en comprimant vos textures. En réalité, le seul inconvénient est les artefacts qui sont inévitablement introduits.
GuyRT

Réponses:

17

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.

Kromster dit de soutenir Monica
la source
2
+1 pour utiliser DXT, c'est une très bonne chose à avoir. Excellente compression et utilisée directement par le GPU, donc les frais généraux sont minimes.
1
Je suis d'accord avec dxt. Vous pouvez également éventuellement interroger le support DXT7 (matériel DX11 +), qui a la même taille que DXT1 mais (apparemment) de meilleure qualité. Cependant, vous devez soit avoir le double de textures (un DXT7 et un DXT1), soit compresser / décompresser pendant le chargement.
Programmdude
5

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?

  • Paniquer et afficher un écran de chargement jusqu'à ce que tous les éléments nécessaires soient chargés. Cela pourrait perturber l'expérience.
  • Ayez des sprites basse résolution pour tout ce qui est préchargé, poursuivez le jeu et remplacez-les dès que le chargement des sprites haute résolution est terminé. Cela peut sembler bon marché pour le joueur.
  • Faites-en un impact sur le jeu et retardez l'événement aussi longtemps que nécessaire. Par exemple, ne créez pas cet ennemi tant que ses graphiques ne sont pas chargés. N'ouvrez pas ce coffre au trésor avant que tous les graphiques de ce butin soient chargés, etc.
API-Beast
la source
J'ai ajouté certaines des exigences que j'ai omises. L'écran de chargement, ou tout type de chargement n'est pas possible. Tout doit être fait en tâche de fond ou entre les ticks individuels (moins de 15 ms pour chacun), alors que la plupart du temps, les préparations de rendu et les mises à jour du jeu sont généralement utilisées. Quoi qu'il en soit, le fractionnement en parties plus petites peut ajouter une certaine flexibilité dans le changement, ce serait certainement plus rapide. La question est de savoir dans quelle mesure les performances lors du rendu sont affectées, car le fait de changer le bitmap source pendant le dessin ralentit le rendu. Je devrais faire une mesure exacte pour dire combien.
Marwin le
@Marwin Impact sur les performances, oui, mais puisque vous utilisez la 2D, vous devriez être encore loin de la question. Si le rendu prend actuellement 1 ms par image et que, grâce à l'utilisation de textures plus petites, il prend soudainement 2 ms, il reste assez rapide pour atteindre une résolution de 60 ips. (16ms)
API-Beast le
@Marwin Le multijoueur est une affaire délicate, a toujours été, sera toujours. Vous devrez très probablement faire des compromis là-bas. Vous allez avoir des béguin, simplement parce que vous devez transférer des données sur Internet, des paquets seront perdus, des pings peuvent soudainement monter, etc. Savoir quand attendre et comment attendre les autres joueurs.
API-Beast le
Bonjour, le bégaiement est presque évitable en multijoueur, nous travaillons actuellement sur ce domaine et je pense que nous avons un bon plan. Je pourrais même poster et répondre à ma propre question décrivant ce que nous avons étudié en détail plus tard :) Cela peut être une surprise, mais le temps de rendu est en réalité un problème. Nous avons effectué de nombreuses optimisations pour accélérer le rendu. Le rendu principal est maintenant effectué dans un fil séparé et d'autres petites modifications. N'oubliez pas que sur le zoom maximal, le joueur peut facilement voir des dizaines de milliers de sprites en même temps. Et nous aimerions même autoriser des niveaux de zoom encore plus élevés ultérieurement.
Marwin le
@Marwin Hm, les objets 10k ne devraient normalement pas poser de problème pour un PC ou un ordinateur portable moderne si vous utilisez le traitement par lots approprié, avez-vous profilé votre code de rendu?
API-Beast le
2

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.

aaaaaaaaaaaa
la source
2

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.

Christian
la source
1

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.

Pablo Ariel
la source
1
Par fractionnement en fichiers, vous voulez dire des fichiers sur disque dur? Je suppose que je pourrais stocker toutes les images sur la RAM pour commencer, et même copier à partir d'une mémoire bitmap vers une vidéo bitmap est trop lent pour le moment, donc le chargement depuis le disque dur serait certainement encore plus lent. Avoir des mimpaps ne m'aidera pas, car j'aurai toujours la plus grande résolution en vram.
Marwin
Oui, vous ne devez pas tout charger, vous ne devez charger que ce que vous utilisez. Chaque fois que vous souhaitez modifier un pixel sur une texture chargée en VRAM, le système doit déplacer l'intégralité de la TEXTURE vers la RAM, il vous suffit de modifier un seul pixel pour le replacer dans la VRAM. Si vous avez tout dans une seule texture, cela implique de déplacer 256 Mo en RAM puis de nouveau en VRAM, ce qui verrouille tout l'ordinateur. Le faire correctement est de l'avoir séparé en différents fichiers et textures.
Pablo Ariel
La modification de la texture qui déclenche la copie en mémoire et en mémoire arrière ne s'applique qu'aux bitmaps persistants, le cache ne sera probablement pas défini sur persistant, le seul inconvénient serait la nécessité de l'actualiser lorsque l'affichage est perdu / trouvé. Mais dans l’allegro, même une simple copie d’une image 640X480 de vram en bitmap mémoire (sauvegarde de la prévisualisation du jeu) prend un certain temps.
Marwin
1
Je dois tout avoir dans une grande texture pour optimiser le dessin lui-même. Sans cela, le changement de contexte entre sprites individuels ralentit trop le rendu, du moins en allegro. Ne vous méprenez pas, mais vous êtes un peu évident capitaine, car vous me suggérez vaguement de faire quelque chose que je demande dans cette question.
Marwin
1
Avoir ces textures mip-mappées dans différents fichiers me forcerait à recharger tout l'atlas lorsque le joueur ferait un zoom avant. Le moteur n'ayant que quelques unités de ms au maximum, je ne vois pas comment le faire.
Marwin le
0

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.

Darxval
la source