Gérer l'état graphique et les composants?

11

J'ai souvent tendance à faire beaucoup d'optimisation prématurée lorsque je traite des graphiques. Il y a quelques principes que j'essaie toujours de suivre:

  • Gardez le nombre de composants D3D au minimum. (États de rendu, tampons, shaders, etc.)
  • Ne liez les composants qu'en cas de nécessité absolue. (Pas déjà lié, etc.)
  • Spécialisez les composants autant que possible. (Définissez uniquement les BindFlags nécessaires, etc.)

Cela m'a amené à construire des wrappers très élaborés pour gérer les composants créés et l'état de pipelage actuel. Non seulement cela consomme beaucoup de mon précieux temps de développement, mais cela ajoute également une autre grande couche de complexité.

Et le pire de tout: je ne sais même pas si cela en vaut la peine.

Certaines de mes considérations d'optimisation peuvent déjà être implémentées à un niveau inférieur et je ne fais que les reproduire, ce qui me fait perdre du temps sur le CPU. D'autres considérations peuvent être totalement inutiles, car l'effet sur les performances est négligeable.

Mes questions sont donc:

  1. Lesquelles des directives ci-dessus sont valables et dans quelle mesure dois-je les suivre?
  2. Comment le GPU gère-t-il les changements d'état?
  3. Que se passe-t-il si je change un état qui n'est jamais utilisé? (Aucun appel de tirage n'est effectué pendant qu'il est actif.)
  4. Quelles sont les pénalités de performances réelles pour lier les différents composants différents?
  5. Quelles autres considérations de performances devraient être prises?

S'il vous plaît, ne me dites pas simplement que je ne devrais pas me soucier des performances jusqu'à ce que j'atteigne les limites réelles. Bien que cela soit évidemment vrai d'un point de vue pratique, je m'intéresse principalement à la théorie. J'ai en quelque sorte besoin de lutter contre l'envie de construire le cadre graphique optimal et je ne pense pas que je puisse le faire avec la "conférence d'optimisation prématurée" habituelle.

Gérer les composants

J'écris actuellement des applications DirectX 11 en C # en utilisant SlimDX comme wrapper managé. C'est un wrapper de très bas niveau et mon abstraction actuelle est construite dessus.

Il existe certains avantages évidents lors de l'utilisation d'une abstraction Direct3D. Configurer l'environnement, charger des shaders, définir des constantes et dessiner un maillage est beaucoup plus simple et utilise beaucoup moins de code. De plus, comme il gère la création et l'élimination de la plupart des composants, ils peuvent être automatiquement réutilisés partout et j'évite presque complètement les fuites de mémoire.

  1. Comment gérez-vous généralement tous les composants et ressources graphiques?
  2. Pouvez-vous recommander des wrappers gérés faisant quelque chose de similaire à mon exemple ci-dessous?

Voici un exemple de mon implémentation actuelle. Je suis assez content de l'interface. Il a suffisamment de flexibilité pour mes besoins et est très simple à utiliser et à comprendre:

// Init D3D environment
var window = new RenderForm();
var d3d = new Direct3D(window, GraphicsSettings.Default);
var graphics = new GraphicsManager(d3d.Device);

// Load assets
var mesh = GeometryPackage.FromFile(d3d, "teapot.gp");
var texture = Texture.FromFile(d3d, "bricks.dds");

// Render states
graphics.SetViewports(new Viewport(0, 0, 800, 600);
graphics.SetRasterizer(wireFrame: false, culling: CullMode.Back);
graphics.SetDepthState(depthEnabled: true, depthWriteEnabled: true);
graphics.SetBlendState(BlendMethod.Transparency);

// Input layout
graphics.SetLayout("effect.fx", "VS", "vs_4_0",
    new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
    new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0)
);

// Vertex shader
graphics.SetShader(Shader.Vertex, "effect.fx", "VS", "vs_4_0");
graphics.SetConstants(Shader.Vertex, 0, 4, stream => stream.Write(wvpMatrix));

// Pixel shader
graphics.SetShader(Shader.Pixel, "effect.fx", "PS", "ps_4_0");
graphics.SetTexture(Shader.Pixel, 0, texture);
graphics.SetSampler(Shader.Pixel, 0, Sampler.AnisotropicWrap);
graphics.SetConstants(Shader.Pixel, 0, 1, stream => stream.Write(new Color4(1, 0, 1, 0);

d3d.Run(() =>
{
    // Draw and present
    d3d.BackBuffer.Clear(new Color4(1, 0, 0.5f, 1));
    graphics.SetOutput(d3d.BackBuffer);
    graphics.Draw(mesh);
    d3d.Present();
}
Lucius
la source
8
Pour ce genre de question, je ne donnerais pas la conférence "Optimisation prématurée", je vous donnerais la conférence "Changements de profil pour que vous puissiez voir par vous-même".
Tetrad
@Tetrad J'ai presque honte d'admettre que c'est un conseil assez décent. Je devrais certainement faire plus de profilage.
Lucius
1
Les numéros de profilage sont la version gamedev de "photos ou cela ne s'est pas produit" à coup sûr =)
Patrick Hughes

Réponses:

3

J'aime l'approche d'abstraction décrite par Hodgman dans ces discussions sur gamedev.net:

Il décrit un système de rendu à trois niveaux:

  1. API de rendu de bas niveau qui accepte les "commandes", ne résumant pas plus que les différences entre les différentes API graphiques, telles que Direct3D 9, Direct3D 11 et OpenGL. Chaque "commande" est mappée à un état ou à un appel d'appel différent, comme la liaison d'un flux ou d'une texture de vertex, ou le dessin des primitives.
  2. API qui accepte les "éléments de rendu", qui regroupent tous les états et un seul appel de dessin nécessaire pour rendre un certain objet, et les trie et les traduit en commandes qui sont envoyées au premier niveau. Un état de rendu contient un appel d'appel et une pile de "groupes d'états", qui regroupent logiquement les changements d'état. Par exemple, vous auriez un groupe d'états pour la passe de rendu, un groupe d'états pour le matériau, un groupe d'états pour la géométrie, un groupe d'états pour l'instance, etc. Ce niveau est chargé de trier ces éléments de rendu pour réduire les changements d'état redondants et de supprimer tous les changements d'état qui sont, en fait, redondants.
  3. Des systèmes de haut niveau comme un graphique de scène ou un moteur de rendu graphique qui envoient des éléments de rendu au deuxième niveau. La chose importante à noter est que ces systèmes ne connaissent ni les algorithmes de tri d'état ni l'API de rendu spécifique, ce qui les rend complètement indépendants de la plate-forme. Ils sont également faciles à utiliser une fois que les API de niveau inférieur ont été implémentées.

En conclusion, ce modèle résout vos deux problèmes à la fois.

jmegaffin
la source