¿Cómo funcionan juntos la CPU y la GPU (hardware y software)?

Hola, supongamos que queremos una forma simple como la foto de abajo. (tomaremos aquí en su mayor parte por software), espero que disfrute el viaje. 🙂

Es cierto que es un ejemplo muy básico, pero creo que es aplicable a otras operaciones de tarjetas gráficas.

Créditos: Oliver Salzbur

La presentación

¿Qué se necesita para tener esa única imagen, que publicaste en tu pregunta, dibujada en la pantalla?

Hay muchas formas de dibujar un triángulo en la pantalla. Por simplicidad, supongamos que no se usaron buffers de vértices. (Un búfer de vértices es un área de memoria donde almacena las coordenadas). Supongamos que el programa simplemente le dijo a la tubería de procesamiento de gráficos sobre cada vértice (un vértice es solo una coordenada en el espacio) en una fila.

Pero , antes de que podamos dibujar algo, primero tenemos que ejecutar algunos andamios. Veremos por qué más tarde:

  // Borrar la pantalla y el búfer de profundidad
 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

 // Restablecer la matriz actual de Modelview
 glMatrixMode (GL_MODELVIEW); 
 glLoadIdentity ();

 // Dibujar usando triángulos
 glBegin (GL_TRIANGLES);

   // Rojo
   glColor3f (1.0f, 0.0f, 0.0f);
   // Parte superior del triángulo (frente)
   glVertex3f (0.0f, 1.0f, 0.0f);

   // Verde
   glColor3f (0.0f, 1.0f, 0.0f);
   // Izquierda del triángulo (frente)
   glVertex3f (-1.0f, -1.0f, 1.0f);

   // azul
   glColor3f (0.0f, 0.0f, 1.0f);
   // Derecha del triángulo (frente)
   glVertex3f (1.0f, -1.0f, 1.0f);

 // Hecho dibujo
 glEnd ();

Entonces, ¿qué hizo eso?

Cuando escribe un programa que quiere usar la tarjeta gráfica, generalmente elegirá algún tipo de interfaz para el controlador. Algunas interfaces conocidas para el controlador son:

  • OpenGL
  • Direct3D
  • CUDA

Para este ejemplo nos quedaremos con OpenGL. Ahora, su interfaz con el controlador es lo que le brinda todas las herramientas que necesita para que su programa hable con la tarjeta gráfica (o el controlador, que luego habla con la tarjeta).

Esta interfaz seguramente le dará ciertas herramientas . Estas herramientas toman la forma de una API a la que puede llamar desde su programa.

Esa API es lo que vemos que se usa en el ejemplo anterior. Miremos más de cerca.

El andamio

Antes de que realmente puedas hacer un dibujo real, tendrás que realizar una configuración . Tienes que definir tu ventana gráfica (el área que realmente se representará), tu perspectiva (la cámara en tu mundo), qué suavizado usarás (para suavizar los bordes de tu triángulo) …

Pero no veremos nada de eso. Solo echaremos un vistazo a las cosas que tendrás que hacer en cada fotograma . Me gusta:

Borrar la pantalla

La canalización de gráficos no va a limpiar la pantalla para usted en cada cuadro. Tendrás que contarlo. ¿Por qué? Esta es la razón por:

Si no limpia la pantalla, simplemente dibujará sobre ella cada fotograma. Es por eso que llamamos glClear con el conjunto GL_COLOR_BUFFER_BIT . El otro bit ( GL_DEPTH_BUFFER_BIT ) le dice a OpenGL que borre el búfer de profundidad . Este búfer se usa para determinar qué píxeles están delante (o detrás) de otros píxeles.

Transformación

Fuente de imagen

La transformación es la parte donde tomamos todas las coordenadas de entrada (los vértices de nuestro triángulo) y aplicamos nuestra matriz ModelView. Esta es la matriz que explica cómo nuestro modelo (los vértices) se rotan, escalan y traducen (mueven).

A continuación, aplicamos nuestra matriz de proyección. Esto mueve todas las coordenadas para que se enfrenten a nuestra cámara correctamente.

Ahora nos transformamos una vez más, con nuestra matriz Viewport. Hacemos esto para escalar nuestro modelo al tamaño de nuestro monitor. ¡Ahora tenemos un conjunto de vértices listos para renderizarse!

Volveremos a la transformación un poco más tarde.

Dibujo

Para dibujar un triángulo, simplemente podemos decirle a OpenGL que comience una nueva lista de triángulos llamando a glBegin con la constante GL_TRIANGLES .

También hay otras formas que puedes dibujar. Como una tira triangular o un abanico triangular. Estas son principalmente optimizaciones, ya que requieren menos comunicación entre la CPU y la GPU para dibujar la misma cantidad de triángulos.

Después de eso, podemos proporcionar una lista de conjuntos de 3 vértices que deberían formar cada triángulo. Cada triángulo usa 3 coordenadas (ya que estamos en el espacio 3D). Además, también proporciono un color para cada vértice, llamando a glColor3f antes de llamar a glVertex3f .

OpenGL calcula automáticamente la sombra entre los 3 vértices (las 3 esquinas del triángulo). Interpolará el color sobre toda la cara del polígono.

Interacción

Ahora, cuando haces clic en la ventana. La aplicación solo tiene que capturar el mensaje de la ventana que indica el clic. Luego puede ejecutar cualquier acción en su programa que desee.

Esto se vuelve mucho más difícil una vez que quieres comenzar a interactuar con tu escena 3D.

Primero debe saber claramente en qué píxel el usuario hizo clic en la ventana. Luego, teniendo en cuenta su perspectiva , puede calcular la dirección de un rayo, desde el punto del clic del mouse en su escena. Luego puede calcular si algún objeto en su escena se cruza con ese rayo. Ahora ya sabe si el usuario hizo clic en un objeto.

Entonces, ¿cómo haces que gire?

Transformación

Soy consciente de dos tipos de transformaciones que generalmente se aplican:

  • Transformación basada en matriz
  • Transformación ósea

La diferencia es que los huesos afectan vértices individuales. Las matrices siempre afectan a todos los vértices dibujados de la misma manera. Veamos un ejemplo.

Ejemplo

Anteriormente, cargamos nuestra matriz de identidad antes de dibujar nuestro triángulo. La matriz de identidad es aquella que simplemente no proporciona transformación alguna . Entonces, lo que dibuje, solo se ve afectado por mi perspectiva. Entonces, el triángulo no se rotará en absoluto.

Si quiero rotarlo ahora, podría hacer los cálculos yo mismo (en la CPU) y simplemente llamar a glVertex3f con otras coordenadas (que se rotan). O podría dejar que la GPU haga todo el trabajo, llamando a glRotatef antes de dibujar:

  // Rotar el triángulo en el eje Y
 glRotatef (cantidad, 0.0f, 1.0f, 0.0f);               

amount es, por supuesto, solo un valor fijo. Si desea animar , tendrá que hacer un seguimiento de la amount y aumentarla en cada cuadro.

Entonces, espera, ¿qué pasó con todas las conversaciones matriciales anteriores?

En este simple ejemplo, no tenemos que preocuparnos por las matrices. Simplemente llamamos glRotatef y se encarga de todo eso por nosotros.

glRotate produce una rotación de grados de angle alrededor del vector xyz. La matriz actual (ver glMatrixMode) se multiplica por una matriz de rotación con el producto reemplazando la matriz actual, como si se llamara a glMultMatrix con la siguiente matriz como argumento:

x 2 ⁡ 1 – c + cx ⁢ y ⁡ 1 – c – z ⁢ sx ⁢ z ⁡ 1 – c + y ⁢ s 0 y ⁢ x ⁡ 1 – c + z ⁢ sy 2 ⁡ 1 – c + cy ⁢ z ⁡ 1 – c – x ⁢ s 0 x ⁢ z ⁡ 1 – c – y ⁢ sy ⁢ z ⁡ 1 – c + x ⁢ sz 2 ⁡ 1 – c + c 0 0 0 0 1

Bueno, gracias por eso!

Conclusión

Lo que se vuelve obvio es que se habla mucho con OpenGL. Pero no nos dice nada. ¿Dónde está la comunicación?

Lo único que nos dice OpenGL en este ejemplo es cuándo está hecho . Cada operación tomará una cierta cantidad de tiempo. Algunas operaciones toman mucho tiempo, otras son increíblemente rápidas.

Enviar un vértice a la GPU será tan rápido que ni siquiera sabría cómo expresarlo. Enviar miles de vértices desde la CPU a la GPU, cada cuadro, es muy probable que no sea un problema.

Limpiar la pantalla puede tomar un milisegundo o peor (tenga en cuenta que generalmente solo tiene unos 16 milisegundos de tiempo para dibujar cada fotograma), dependiendo de qué tan grande sea su ventana gráfica. Para borrarlo, OpenGL tiene que dibujar cada píxel en el color que desea borrar, que podrían ser millones de píxeles.

Aparte de eso, solo podemos preguntarle a OpenGL sobre las capacidades de nuestro adaptador de gráficos (resolución máxima, suavizado máximo, profundidad de color máxima, …).

Pero también podemos llenar una textura con píxeles que tienen un color específico. Cada píxel tiene un valor y la textura es un “archivo” gigante lleno de datos. Podemos cargar eso en la tarjeta gráfica (creando un búfer de textura), luego cargar un sombreador, decirle al sombreador que use nuestra textura como entrada y ejecutar algunos cálculos extremadamente pesados ​​en nuestro “archivo”.

Entonces podemos “renderizar” el resultado de nuestro cálculo (en forma de nuevos colores) en una nueva textura.

Así es como puede hacer que la GPU funcione para usted de otras maneras. Asumo que CUDA funciona de manera similar a ese aspecto, pero nunca tuve la oportunidad de trabajar con él.

Realmente solo tocamos un poco todo el tema. La programación de gráficos en 3D es una bestia infernal.

Fuente de imagen

En resumen, su dilema se acabaría si simplemente intentara considerar una GPU como una extensión de la CPU. ; –

Una GPU es una unidad de procesamiento gráfico. Puede existir en dos formas. Una es una GPU dedicada, o una colección de GPU, que pueden no tener salida de video. La otra es una tarjeta de video que contiene una GPU. La interfaz de hardware puede ser la interfaz de tarjeta ordinaria o algo exclusivo de la matriz de GPU. A diferencia de una tarjeta de video ordinaria, una GPU tiene una interfaz bidireccional, lo que permite que la CPU envíe código y datos a la GPU y reciba los resultados del cálculo. Si bien el conjunto de instrucciones de una GPU es limitado, es muy, muy rápido en comparación con la velocidad de la CPU. Esto permite la ejecución rápida de programas que pueden utilizar la potencia de procesamiento de una GPU.