Comment charger des ressources graphiques de manière asynchrone?

9

Pensons indépendamment de la plateforme: je veux charger des ressources graphiques pendant que le reste du jeu tourne.

En principe, je peux charger les fichiers réels sur un thread séparé ou utiliser des E / S asynchrones. Mais avec des objets graphiques, je devrai les télécharger sur le GPU, et cela ne peut (généralement) se faire que sur le thread principal.

Je peux changer ma boucle de jeu pour ressembler à ceci:

while true do
    update()
    for each pending resource do
        load resource to gpu
    end
    draw()
end

tout en ayant un thread séparé chargez les ressources du disque vers la RAM.

Cependant, s'il y a beaucoup de grandes ressources à charger, cela pourrait me faire manquer une échéance de trame et éventuellement perdre des trames. Je peux donc changer la boucle en ceci:

while true do
    update()
    if there are pending resources then
        load one resource to gpu
        remove that resource from the pending list
    end
    draw()
end

Chargement efficace d'une seule ressource par trame. Cependant, s'il y a beaucoup de petites ressources à charger, le chargement de toutes prendra beaucoup de trames et il y aura beaucoup de temps perdu.

Idéalement, je voudrais chronométrer mon chargement de la manière suivante:

while true do
    time_start = get_time()
    update()
    while there are pending resources then
        current_time = get_time()
        if (current_time - time_start) + time_to_load(resource) >= 1/60 then
            break
        load one resource to gpu
        remove that resource from the pending list
    end
    draw()
end

De cette façon, je ne chargerais une ressource que si je peux le faire dans le temps dont je dispose pour ce cadre. Malheureusement, cela nécessite un moyen d'estimer le temps qu'il faut pour charger une ressource donnée, et pour autant que je sache, il n'y a généralement aucun moyen de le faire.

Qu'est-ce que j'oublie ici? Comment de nombreux jeux arrivent-ils à charger tous leurs trucs de manière complètement asynchrone et sans perte d'images ou temps de chargement extrêmement longs?

Pyjama Panda
la source

Réponses:

7

Commençons par supposer un monde parfait. Il y a deux étapes pour charger une ressource: d'abord, vous la retirez de votre support de stockage et dans la mémoire au format correct, et ensuite vous la transférez via le bus mémoire vers la mémoire vidéo. Aucune de ces deux étapes n'a réellement besoin d'utiliser du temps sur votre thread principal - il suffit de s'impliquer pour émettre une commande d'E / S. Votre CPU et votre GPU peuvent continuer à faire d'autres choses pendant la copie de la ressource. La seule véritable ressource consommée est la bande passante mémoire.

Si vous utilisez une plate-forme sans grande couche d'abstraction entre vous et le matériel, l'API expose probablement ces concepts directement. Mais si vous êtes sur un PC, il y a probablement un pilote entre vous et le GPU, et il veut faire les choses à sa manière. En fonction de l'API, vous pourrez peut- être créer une texture qui est sauvegardée par la mémoire que vous possédez, mais plus probablement appeler l'API "créer une texture" copiera la texture dans une certaine mémoire que le pilote possède. Dans ce cas, la création d'une texture aura une surcharge fixe et un temps proportionnel à la taille de la texture. Après cela, le pilote peut faire quoi que ce soit - il peut transférer proactivement la texture vers VRAM, ou il ne peut pas déranger le téléchargement de la texture jusqu'à ce que vous tentiez de le rendre pour la première fois.

Vous pouvez ou non être en mesure de faire quelque chose à ce sujet, mais vous pouvez faire une estimation approximative du temps qu'il faut pour faire l'appel "créer une texture". Bien sûr, tous les chiffres vont changer en fonction du matériel et des logiciels, donc cela ne vaut probablement pas la peine de passer beaucoup de temps à les inverser. Alors essayez-le et voyez! Choisissez une métrique: "nombre de textures par image" ou "taille totale des textures par image", choisissez un quota (par exemple, 4 textures par image), et commencez à le tester.

Dans les cas pathologiques, vous devrez peut-être même garder une trace des deux quotas en même temps (par exemple, limiter à 4 textures par image ou 2 Mo de textures par image, selon la valeur la plus faible). Mais le vrai truc à la plupart streaming texture est de déterminer qui les textures que vous voulez intégrer dans votre mémoire limitée, pas combien de temps il faut pour les copier autour.

En outre, les cas pathologiques pour la création de texture - comme de nombreuses petites textures nécessaires en même temps - ont également tendance à être des cas pathologiques pour d'autres domaines. Cela vaut la peine d'obtenir une implémentation de travail simple avant de se soucier du nombre exact de microsecondes qu'une texture prend pour copier. (De plus, les performances réelles ne peuvent pas être encourues en tant que temps CPU sur l'appel "créer une texture", mais plutôt en tant que temps GPU sur la première image que vous utilisez la texture.)

John Calsbeek
la source
Voilà une assez bonne explication. Beaucoup de choses que je ne connaissais pas mais qui ont beaucoup de sens. Au lieu de le tester sous contrainte, je mesurerais la surcharge de création de texture lors de l'exécution, commencerais doucement et réduirais pour dire, 80% du temps d'exécution disponible pour laisser de la place aux valeurs aberrantes.
Panda Pyjama
@PandaPajama Je suis un peu sceptique à ce sujet. Je m'attendrais à ce que l'état d'équilibre soit «aucune texture à copier» et une énorme variance. Et comme je l'ai dit, je soupçonne qu'une partie du hit est le premier cadre de rendu qui utilise la texture, ce qui est beaucoup plus difficile à mesurer dynamiquement sans affecter les performances.
John Calsbeek
En outre, voici une présentation NVIDIA sur les transferts de texture asynchrones. Pour autant que je sache, l'essentiel, c'est que cela rentre à la maison, c'est que l'utilisation d'une texture trop tôt après le téléchargement va caler. developer.download.nvidia.com/GTC/PDF/GTC2012/PresentationPDF/…
John Calsbeek
Je ne suis pas un pilote de développement, mais est-ce courant? Il n'est pas très logique d'implémenter les pilotes de cette manière, car les premières utilisations de la texture sont très susceptibles de se présenter sous forme de pointes (comme au début de chaque niveau) au lieu d'être espacées le long de la chronologie.
Panda Pyjama
@PandaPajama Il est également courant que les applications créent plus de textures qu'il n'y a de VRAM disponible, et créent des textures et ne les utilisent jamais. Un cas courant est «créer un tas de textures et puis dessiner immédiatement une scène qui les utilise», auquel cas être paresseux aide le conducteur, car il peut déterminer quelles textures sont réellement utilisées, et cette première image va de toute façon se bloquer. . Mais je ne suis pas non plus un pilote de développement, prenez-le avec un grain de sel (et testez-le!).
John Calsbeek