Le coût réel de tout changement d'état particulier varie en fonction de tant de facteurs qu'une réponse générale est presque impossible.
Premièrement, chaque changement d'état peut potentiellement avoir à la fois un coût côté processeur et un coût côté GPU. Le coût du processeur peut, en fonction de votre pilote et de votre API graphique, être payé entièrement sur le thread principal ou partiellement sur un thread d'arrière-plan.
Deuxièmement, le coût du GPU peut dépendre de la quantité de travail en vol. Les GPU modernes sont très pipelinés et aiment obtenir beaucoup de travail en vol à la fois, et le plus gros ralentissement que vous pouvez obtenir est de bloquer le pipeline afin que tout ce qui est actuellement en vol doit se retirer avant le changement d'état. Qu'est-ce qui peut provoquer un blocage du pipeline? Eh bien, cela dépend de votre GPU!
La chose que vous devez réellement savoir pour comprendre les performances est la suivante: que doivent faire le pilote et le GPU pour traiter votre changement d'état? Cela dépend bien sûr de votre GPU, ainsi que des détails que les éditeurs de logiciels ne partagent souvent pas publiquement. Cependant, il existe certains principes généraux .
Les GPU sont généralement divisés en un frontend et un backend. Le frontend gère un flux de commandes générées par le pilote, tandis que le backend fait tout le vrai travail. Comme je l'ai déjà dit, le backend aime avoir beaucoup de travail en vol, mais il a besoin d'informations pour stocker des informations sur ce travail (peut-être remplies par le frontend). Si vous lancez suffisamment de petits lots et utilisez tout le silicium pour suivre le travail, le front-end devra caler même s'il y a beaucoup de puissance inutilisée. Donc, un principe ici: plus il y a de changements d'état (et de petits tirages), plus vous risquez de mourir de faim le backend GPU .
Pendant qu'un dessin est en cours de traitement, vous exécutez simplement des programmes de shader, qui font des accès à la mémoire pour récupérer vos uniformes, vos données de tampon de vertex, vos textures, mais aussi les structures de contrôle qui indiquent aux unités de shader où vos vertex tampons et vos textures le sont. Et le GPU a également des caches devant ces accès à la mémoire. Donc, chaque fois que vous lancez de nouveaux uniformes ou de nouvelles liaisons de texture / tampon sur le GPU, il manquera probablement un cache lors de la première lecture. Autre principe: la plupart des changements d'état entraîneront un échec du cache GPU.(Ceci est plus significatif lorsque vous gérez vous-même des tampons constants: si vous conservez les mêmes tampons entre les tirages, ils sont plus susceptibles de rester en cache sur le GPU.)
Une grande partie du coût des changements d'état pour les ressources de shader est le côté CPU. Chaque fois que vous définissez un nouveau tampon constant, le pilote copie très probablement le contenu de ce tampon constant dans un flux de commandes pour le GPU. Si vous définissez un seul uniforme, le pilote le transforme très probablement en un grand tampon constant derrière votre dos, il doit donc rechercher le décalage pour cet uniforme dans le tampon constant, copier la valeur dans, puis marquer le tampon constant aussi sale afin qu'il puisse être copié dans le flux de commandes avant le prochain appel de tirage. Si vous liez une nouvelle texture ou un tampon de vertex, le pilote copie probablement une structure de contrôle pour cette ressource. De plus, si vous utilisez un GPU discret sur un système d'exploitation multitâche, le pilote doit suivre chaque ressource que vous utilisez et quand vous commencez à l'utiliser pour que le noyau ' s Le gestionnaire de mémoire du GPU peut garantir que la mémoire de cette ressource réside dans la VRAM du GPU lors du tirage. Principe:les changements d'état font que le pilote mélange la mémoire pour générer un flux de commandes minimal pour le GPU.
Lorsque vous modifiez le shader actuel, vous provoquez probablement un échec de cache GPU (ils ont aussi un cache d'instructions!). En principe, le travail du processeur devrait se limiter à mettre une nouvelle commande dans le flux de commandes en disant "utiliser le shader". En réalité, cependant, il y a tout un tas de compilation de shaders à gérer. Les pilotes GPU compilent très souvent paresseusement des shaders, même si vous avez créé le shader à l'avance. Plus pertinent à ce sujet, cependant, certains états ne sont pas pris en charge nativement par le matériel GPU et sont plutôt compilés dans le programme shader. Un exemple populaire est les formats de vertex: ceux-ci peuvent être compilés dans le vertex shader au lieu d'être un état séparé sur la puce. Donc, si vous utilisez des formats de sommets que vous n'avez pas utilisés avec un vertex shader particulier auparavant, vous pouvez maintenant payer un tas de coûts CPU pour patcher le shader et copier le programme shader sur le GPU. De plus, le pilote et le compilateur de shader peuvent conspirer pour faire toutes sortes de choses afin d'optimiser l'exécution du programme de shader. Cela peut signifier l'optimisation de la disposition de la mémoire de vos uniformes et structures de contrôle des ressources afin qu'elles soient bien emballées dans la mémoire adjacente ou les registres de shader. Ainsi, lorsque vous modifiez des shaders, le pilote peut regarder tout ce que vous avez déjà lié au pipeline et le reconditionner dans un format entièrement différent pour le nouveau shader, puis le copier dans le flux de commandes. Principe: Cela peut signifier l'optimisation de la disposition de la mémoire de vos uniformes et structures de contrôle des ressources afin qu'elles soient bien emballées dans la mémoire adjacente ou les registres de shader. Ainsi, lorsque vous modifiez des shaders, le pilote peut regarder tout ce que vous avez déjà lié au pipeline et le reconditionner dans un format entièrement différent pour le nouveau shader, puis le copier dans le flux de commandes. Principe: Cela peut signifier l'optimisation de la disposition de la mémoire de vos uniformes et structures de contrôle des ressources afin qu'elles soient bien emballées dans la mémoire adjacente ou les registres de shader. Ainsi, lorsque vous modifiez des shaders, le pilote peut regarder tout ce que vous avez déjà lié au pipeline et le reconditionner dans un format entièrement différent pour le nouveau shader, puis le copier dans le flux de commandes. Principe:la modification des shaders peut entraîner un brassage important de la mémoire du processeur.
Les modifications du tampon de trame sont probablement les plus dépendantes de l'implémentation, mais sont généralement assez chères sur le GPU. Votre GPU peut ne pas être en mesure de gérer plusieurs appels de tirage vers différentes cibles de rendu en même temps, il peut donc être nécessaire de bloquer le pipeline entre ces deux appels de tirage. Il peut être nécessaire de vider les caches pour que la cible de rendu puisse être lue ultérieurement. Il peut être nécessaire de résoudre le travail qu'il a reporté pendant le dessin. (Il est très courant d'accumuler une structure de données distincte avec des tampons de profondeur, des cibles de rendu MSAA, etc.). Cela peut devoir être finalisé lorsque vous quittez cette cible de rendu. Si vous utilisez un GPU basé sur des tuiles , comme de nombreux GPU mobiles, une quantité assez importante de travail d'ombrage réel devra peut-être être nettoyée lorsque vous vous éloignerez d'un tampon de trame.) Principe:changer les cibles de rendu coûte cher sur le GPU.
Je suis sûr que tout cela est très déroutant, et malheureusement, il est difficile d'être trop précis car les détails ne sont souvent pas publics, mais j'espère que c'est un aperçu à moitié décent de certaines des choses qui se passent réellement lorsque vous appelez un État fonction changeante dans votre API graphique préférée.