En esta entrada voy a definir como funciona un procesador de comandos o más bien como funcionan los procesadores de comandos que son una parte esencial del funcionamiento de una GPU y a través de discusiones en el Discord y en los comentarios del blog tengo la sensación que mucha gente no entiende cual es la tarea del procesador de comandos de una GPU.

to-do-list-compressor.jpg

La CPU genera una lista de comandos gráficosque le dicea la GPU que tiene en ese fotograma. Esta es creada por cada fotograma por lo que es dinámica. Esto lo hace a través de la API que puede ser de alto nivel donde se traduce a un lenguaje común que el controlador de la GPU entiende y de ahí al microcódigo de la GPU, o de bajo nivel donde la aplicación genera directamente el microcódigo para la GPU en ese fotograma.

Dicha lista una vez generada es enviada a una memoria que puede ser la RAM del sistema o una privada de la GPU en una zona de direcciones de memoria que la GPU trata como un anillo, es decir que cuando llega a la última dirección de memoria asignada del anillo cuando lee la siguiente instrucción se pone automaticamente a 0 el contador de programa y asi recursivamente. Cada anillo completo es un fotograma por lo que cuando el procesador de comandos llega al final del anillo de datos o ring búfer empieza de 0 y con ello empieza un fotograma nuevo.

gpu_commands.png

La lista de comandos es generada por la CPU, pero también puede ser manipulada por el procesador de comandos de la propia GPU al crearse nuevas instrucciones durante el proceso que son los Indirect Buffer. Pero su funcionamiento es del tipo FIFO del ingles First In – First Out (“Primero en entrar primero en salir”) esto quiere decir que el elemento que entre primero a la Cola sera el primero que salga y el último que entre sera el último en salir. Por lo que todo comando indirecto será procesado al final de la cola. En la GPU tenemos por lista de comandos un Ring Buffer, de tal manera que si queremos tener varios contextos (listas de comandos) funcionando al mismo tiempo en paralelo debemos tener varios anillos.

Normalmente solemenos tener dos tipos de anillos distintos, uno para computación y el otro para gráficos, es comun tener un solo anillo para gráficos pero si queremos renderizar en estereo entonces nos interesa tener dos anillos de comandos gráficos simétricos. En algunos casos en concreto tenemos dos anillos de comandos gráficos diferentes, esto es porque tenemos dos procesadores de comandos gráficos distintos que utilizan un microcodigo distinto. En ese caso funcionan conmutados al uno con el otro.

En el caso de los anillos para computación es distinto, su duración no depende del fotograma entero y suelen estar compuestos por varios sub-anillos. De esta manera pueden saltar de un hilo/anillo a otro muy fácilmente. Por ejemplo la actual arquitectura GCN soporta por cada procesador de comandos de computación (ACE) hasta unos 8 sub-anillos distintos que corresponden cada uno a una sub-lista.

Ahora bien, como he comentado antes la lista o listas de comandos puede ser manipulada por la GPU. Por ejemplo si tenemos un mecanismo de eliminación de fragmentos superfluos entonces dicho mecanismo de eliminación eliminará los comandos correspondientes a los fragmentos no visibles. Tambien nos podemos encontrar que la aplicación cree nuevos comandos de manera dinámica pero aqui es donde viene uno de los problemas de rendimiento, si la siguiente instrucción es dependiente de la ejecución de una instrucción entonces si tenemos un solo anillo se para en seco pero si tenemos varios podemos enviar a un anillo de computación una instrucción concreta y que luego el resultado sea escrito en el anillo de comandos gráficos.

Antes para simplificar os he comentado que el tamaño de la lista de comandos para gráficos es un anillo es fija en lo que la cantidad de direcciones de memoria se refiere. Eso una sobresimplificación que es una falsedad porque la complejidad de cada fotograma es distinta. El final de fotograma llega cuando se envia una instrucción que coloca el contador de programa del procesador de comandos a la dirección inicial y borra el resto del contenido.

A la hora de generar una lista de comandos gráficos en el Ring Buffer correspondiente, la GPU no puede acceder a ella hasta que no se le de la orden de copiar dicha lista de comandos y llevarla a su direccionamiento de memoria o a una memoria interna. El tiempo de generación de la lista de comandos depende de la complejidad de la escena. Si tenemos una enorme cantidad de elementos en los que la CPU tiene que calcular su situación entonces el tiempo que tarde la CPU será más alto y le tomara más milisegundos en crear la lista de comandos, por eso es importante tener una buena GPU, tambien se puede utilizar los anillos de computación para calculos como es la detección de colisiones (lo cual será ampliamente acelerado con las RT Cores) o las fisicas que nos sirve para calcular la reacción de dos elementos entre si.

El uso de la GPU para acelerar la generación de las listas de comandos es algo nuevo de esta generación que nos ocupa y se va a acelerar enormemente en la siguiente con el uso de los Tensor Cores y los RT Cores para el procesamiento de las físicas y la detección de colisiones. De cara a la siguiente generación no vamos a ver un aumento en el rendimiento por el uso de Jaguar a Ryzen, más que nada porque ni con Ryzen se va a llegar a la capacidad de cálculo que tienen las GPUs actuales a la hora de procesar las fisicas.

2918553-old_xdk_ubisoft_benchmarkubisoft-cloth-simulation-ps4-vs-ps3

Con Ryzen solo y sin cambios en las GPUs obtendriamos un salto que muy seguramente nos permitiría colocar ciertos juegos a 60fps e incluso aptos para la VR pero eso sabría a poco. Con elemento nuevos como los Tensor Cores y los RT Cores podemos acelerar el procesamiento de la física de escena hasta una orden de magnitud mayor utilizando estos via pipeline de computación en vez de las unidades vectoriales/SIMD convencionales para ello.

¿Y por qué no procesar varios fotogramas al mismo tiempo con varios anillos gráficos distintos y varios procesadores de comandos? No tenemos varios anillos gráficos que correspondan a escenas distintas por el simple hecho que solo procesamos una escena cada vez y todos los elementos interactuan entre si, por lo que hasta que no todos los elementos de la escena no tienen su situación procesada no se puede generar una nueva lista y la actual no se puede eliminar hasta que la GPU termine de renderizar la escena. En el el caso de escenas en estereo en realidad comparten la misma lista de comandos por el hecho de ser dos posiciones de cámara distintas de una misma escena.

Desde que el momento en que la escena es interactiva no sabemos como afectarán las acciones del jugador a la escena no podemos predecir los fotogramas siguientes. ¿Os acordáis de la teoria del caos? Pues más o menos es algo similar y a medida que tenemos sistemas más complejos con mayor o menor interactividad más impredecible es el resultado incluso dentro de unas reglas concretas.

Las acciones del jugador dependerán del estimulo visual que él/ella reciba mientras este jugando por lo que es una estupidez renderizar varios contextos distintos correspondientes a cada uno, más que nada porque para realizar uno necesitamos el contexto anterior como base que es la anterior lista de comandos y de ahí a que no podamos procesar varias en un entorno interactivo.

Y con esto termino, ya sabéis, podéis comentar lo que queráis o ir al Discord del blog para comentar las entradas.