Me gusta pensar en el rendimiento en términos de " límites ". Es una forma práctica de conceptualizar un sistema interconectado bastante complicado. Cuando tienes un problema de rendimiento, haces la pregunta: "¿Qué límites estoy alcanzando?" (O: "¿Estoy vinculado a la CPU / GPU?")
Puedes dividirlo en múltiples niveles. En el nivel más alto tienes la CPU y la GPU. Puede estar vinculado a la CPU (GPU inactivo esperando a la CPU), o vinculado a la GPU (la CPU está esperando en la GPU). Aquí hay una buena publicación de blog sobre el tema.
Puedes desglosarlo aún más. En el lado de la CPU , es posible que esté utilizando todos sus ciclos en los datos que ya están en el caché de la CPU. O puede que tenga memoria limitada , dejando la CPU inactiva esperando que los datos entren desde la memoria principal ( así que optimice su diseño de datos ). Podrías descomponerlo aún más.
(Si bien estoy haciendo una amplia descripción del rendimiento con respecto a XNA, señalaré que una asignación de un tipo de referencia ( class
no struct
), aunque normalmente es barata, podría activar el recolector de basura, que quemará muchos ciclos, especialmente en Xbox 360 . Vea aquí para más detalles).
En el lado de la GPU , comenzaré señalando esta excelente publicación de blog que tiene muchos detalles. Si desea un nivel de detalle loco en la tubería, lea esta serie de publicaciones de blog . ( Aquí hay uno más simple ).
En pocas palabras, algunos de los grandes son: " límite de relleno " (cuántos píxeles puede escribir en el backbuffer, a menudo, cuánto sobregiro puede tener), " límite de sombreado " (cuán complicados pueden ser sus sombreadores y cuántos datos puede pasar a través de ellos), " límite de ancho de banda de textura / captación de textura " (cuántos datos de textura puede acceder).
Y, ahora, llegamos al punto más grande, que es lo que realmente está preguntando, donde la CPU y la GPU tienen que interactuar (a través de las diversas API y controladores). En términos generales, existe el " límite de lote " y el " ancho de banda ". (Tenga en cuenta que la primera parte de la serie que mencioné anteriormente entra en detalles extensos ).
Pero, básicamente, un lote ( como ya sabe ) ocurre cada vez que llama a una de las GraphicsDevice.Draw*
funciones (o parte de XNA, por ejemplo SpriteBatch
, hace esto por usted). Como sin duda ya has leído, obtienes algunos miles * de estos por fotograma. Este es un límite de CPU, por lo que compite con su otro uso de CPU. Básicamente, es el controlador que empaqueta todo lo que le ha dicho que dibuje y lo envía a la GPU.
Y luego está el ancho de banda de la GPU. Esta es la cantidad de datos sin procesar que puede transferir allí. Esto incluye toda la información de estado que acompaña a los lotes, desde establecer el estado de representación y las constantes / parámetros del sombreador (que incluye elementos como matrices de mundo / vista / proyecto), hasta vértices cuando se usan las DrawUser*
funciones. También incluye todas las llamadas a SetData
y GetData
en texturas, tampones de vértices, etc.
En este punto, debo decir que todo lo que pueda llamar SetData
(texturas, búferes de vértices e índices, etc.), así como Effect
s - permanece en la memoria de la GPU. No se reenvía constantemente a la GPU. Un comando de dibujo que hace referencia a esos datos simplemente se envía con un puntero a esos datos.
(Además: solo puede enviar comandos de dibujo desde el hilo principal, pero puede SetData
hacerlo en cualquier hilo).
XNA complica las cosas un poco con sus clases de estado render ( BlendState
, DepthStencilState
, etc.). Estos datos de estado se envían por llamada de sorteo (en cada lote). No estoy 100% seguro, pero tengo la impresión de que se envía perezosamente (solo envía un estado que cambia). De cualquier manera, los cambios de estado son baratos hasta el punto de ser gratuitos, en relación con el costo de un lote.
Finalmente, lo último que hay que mencionar es la canalización interna de la GPU . No desea forzarlo a que se vacíe escribiendo en datos que aún necesita leer, o leyendo datos que aún necesita escribir. Una descarga de tubería significa que espera a que finalicen las operaciones, de modo que todo esté en un estado coherente cuando se accede a los datos.
Los dos casos particulares a tener en cuenta son: Invocar GetData
cualquier cosa dinámica, particularmente en una en la RenderTarget2D
que la GPU pueda estar escribiendo. Esto es extremadamente malo para el rendimiento, no lo hagas.
El otro caso es llamar SetData
a los búferes de vértices / índices. Si necesita hacer esto con frecuencia, use un DynamicVertexBuffer
(también DynamicIndexBuffer
). Esto le permite a la GPU saber que cambiarán con frecuencia y hacer algo de magia interna para evitar el enjuague de la tubería.
(Tenga en cuenta también que los búferes dinámicos son más rápidos que los DrawUser*
métodos, pero tienen que asignarse previamente al tamaño máximo requerido).
... Y eso es casi todo lo que sé sobre el rendimiento de XNA :)