Información sobre renderizado, lotes, la tarjeta gráfica, rendimiento, etc. + XNA?

12

Sé que el título es un poco vago, pero es difícil describir lo que realmente estoy buscando, pero aquí va.

Cuando se trata de la representación de la CPU, el rendimiento es principalmente fácil de estimar y directo, pero cuando se trata de la GPU debido a mi falta de información técnica, no tengo ni idea. Estoy usando XNA, así que sería bueno si la teoría pudiera estar relacionada con eso.

Entonces, lo que realmente quiero saber es, ¿qué sucede cuándo y dónde (CPU / GPU) cuando haces acciones de dibujo específicas? ¿Qué es un lote? ¿Qué influencia tienen los efectos, proyecciones, etc.? ¿Los datos persisten en la tarjeta gráfica o se transfieren en cada paso? Cuando se habla de ancho de banda, ¿estás hablando del ancho de banda interno de una tarjeta gráfica o de la tubería de la CPU a la GPU?
Nota: en realidad no estoy buscando información sobre cómo ocurre el proceso de dibujo, ese es el negocio de la GPU, estoy interesado en todos los gastos generales que preceden a eso.

Me gustaría entender qué sucede cuando realizo la acción X, para adaptar mis arquitecturas y prácticas a eso.

Se agradece mucho cualquier artículo (posiblemente con ejemplos de código), información, enlaces, tutoriales que den más información sobre cómo escribir mejores juegos. Gracias :)

Aidiakapi
fuente
2
Aunque originalmente era XNA, agregué la etiqueta DirectX, ya que esa es la tecnología subyacente, podría ayudarlo a obtener mejores respuestas. También revise esta respuesta que podría darle un buen punto de partida.
Andrew Russell el
@AndrewRussell Muchas gracias :). De hecho, ya he leído varios artículos sobre el tema, incluido ese. Pero no ha cubierto todo lo que me gustaría saber.
Aidiakapi

Respuestas:

20

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 ( classno 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 SetDatay GetDataen 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 Effects - 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 SetDatahacerlo 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 GetDatacualquier cosa dinámica, particularmente en una en la RenderTarget2Dque la GPU pueda estar escribiendo. Esto es extremadamente malo para el rendimiento, no lo hagas.

El otro caso es llamar SetDataa 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 :)

Andrew Russell
fuente
¡Muchas gracias! Esto es exactamente lo que estaba buscando y esperando :).
Aidiakapi
1
Unos cientos de lotes por cuadro suenan demasiado pesimistas. La regla general que siempre he escuchado es lotes de 2K a 3K por cuadro. Se sabe que algunos juegos obtienen hasta 10K en PC, pero creo que eso requiere mucho cuidado para lograrlo.
Nathan Reed
Muy bien. La cifra de "unos pocos cientos" proviene del documento "lote por lotes", que enumeraba "25k lotes / s al 100% de una CPU de 1 GHz". Pero ese documento tiene ahora una década, y los controladores y las CPU han mejorado significativamente desde entonces. Actualizaré esto (y mis otros) para leer "unos pocos miles".
Andrew Russell