¿Gestión de estado gráfico y componentes?

11

A menudo tiendo a hacer muchas optimizaciones prematuras cuando trato con gráficos. Hay algunos principios que siempre trato de seguir:

  • Mantenga el número de componentes D3D al mínimo. (Estados de renderizado, buffers, sombreadores, etc.)
  • Solo unir componentes si es absolutamente necesario. (No limitado ya, etc.)
  • Especialice los componentes tanto como sea posible. (Solo configure BindFlags necesarios, etc.)

Esto me llevó a construir envoltorios muy elaborados para administrar los componentes creados y el estado actual de las tuberías. Esto no solo consume mucho de mi valioso tiempo de desarrollo, sino que también agrega otra gran capa de complejidad.

Y lo peor de todo: ni siquiera sé si vale la pena.

Algunas de mis consideraciones de optimización ya pueden implementarse en un nivel inferior y solo las estoy replicando, además de perder tiempo en la CPU. Otras consideraciones pueden ser completamente innecesarias, ya que el efecto sobre el rendimiento es insignificante.

Entonces mis preguntas son:

  1. ¿Cuáles de las pautas anteriores son válidas y en qué medida debo seguirlas?
  2. ¿Cómo maneja la GPU los cambios de estado?
  3. ¿Qué sucede si cambio un estado que nunca se usa? (No se realiza ninguna llamada de sorteo mientras está activa).
  4. ¿Cuáles son las sanciones de rendimiento reales por vincular los diferentes componentes?
  5. ¿Qué otras consideraciones de rendimiento deben hacerse?

Por favor, no me diga simplemente que no debería importarme el rendimiento hasta que alcance los límites reales. Si bien eso es obviamente cierto desde un punto de vista práctico, estoy principalmente interesado en la teoría. De alguna manera necesito combatir el impulso de construir el marco gráfico óptimo y no creo que pueda hacerlo con la habitual "conferencia de optimización prematura".

Administrar componentes

Actualmente estoy escribiendo aplicaciones DirectX 11 en C # usando SlimDX como contenedor administrado. Es un contenedor de muy bajo nivel y mi abstracción actual se basa en él.

Hay algunas ventajas obvias cuando se usa una abstracción Direct3D. Configurar el entorno, cargar sombreadores, configurar constantes y dibujar una malla es mucho más simple y utiliza mucho menos código. Además, dado que gestiona la creación y eliminación de la mayoría de los componentes, pueden reutilizarse automáticamente en todas partes y evito casi por completo las pérdidas de memoria.

  1. ¿Cómo gestiona habitualmente todos los componentes y recursos gráficos?
  2. ¿Me puede recomendar algún contenedor administrado que haga algo similar a mi ejemplo a continuación?

Aquí hay un ejemplo de mi implementación actual. Estoy bastante contento con la interfaz. Tiene suficiente flexibilidad para mis necesidades y es muy simple de usar y comprender:

// 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
fuente
8
Para este tipo de pregunta no daría la conferencia de "optimización prematura", le daría la conferencia "cambios de perfil para que pueda ver por sí mismo".
Tetrad
@Tetrad Estoy casi avergonzado de admitir que este es un consejo bastante decente. Definitivamente debería hacer más perfiles.
Lucius
1
Los números de perfil son la versión de gamedev de "fotos o no sucedió" con seguridad =)
Patrick Hughes

Respuestas:

3

Me gusta el enfoque de abstracción descrito por Hodgman en estos hilos en gamedev.net:

Describe un sistema de representación de tres niveles:

  1. API de representación de bajo nivel que acepta "comandos", abstrayendo no más que las diferencias entre las diferentes API de gráficos, como Direct3D 9, Direct3D 11 y OpenGL. Cada "comando" se asigna a un estado diferente o llamada de dibujo, como unir una secuencia o textura de vértice, o dibujar las primitivas.
  2. API que acepta "elementos de representación", que agrupan todos los estados y una sola llamada de sorteo necesaria para representar un determinado objeto, y los ordena y traduce en comandos que se envían al primer nivel. Un estado de representación contiene una llamada de sorteo y una pila de "grupos de estado", que agrupan lógicamente los cambios de estado. Por ejemplo, tendría un grupo de estado para el pase de representación, un grupo de estado para el material, un grupo de estado para la geometría, un grupo de estado para la instancia, etc. Este nivel es responsable de ordenar estos elementos de representación para reducir los cambios de estado redundantes y eliminar cualquier cambio de estado que, de hecho, sea redundante.
  3. Sistemas de alto nivel como un gráfico de escena o un renderizador de GUI que envían elementos de renderizado al segundo nivel. Lo importante a tener en cuenta es que estos sistemas no conocen ni los algoritmos de clasificación de estado ni la API de representación específica, lo que los hace completamente independientes de la plataforma. También son fáciles de usar una vez que se han implementado las API de nivel inferior.

En conclusión, este modelo resuelve ambos problemas a la vez.

jmegaffin
fuente