¿Cómo interactúan la CPU y la GPU al mostrar gráficos de computadora?

58

Aquí puede ver una captura de pantalla de un pequeño programa C ++ llamado Triangle.exe con un triángulo giratorio basado en la API OpenGL.

ingrese la descripción de la imagen aquí

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

Tenía curiosidad y quería saber todo el proceso desde hacer doble clic en Triangle.exe en Windows XP hasta que pueda ver el triángulo girando en el monitor. ¿Qué sucede, cómo interactúan la CPU (que primero maneja el .exe) y la GPU (que finalmente muestra el triángulo en la pantalla)?

Supongo que participar en la visualización de este triángulo giratorio es principalmente el siguiente hardware / software, entre otros:

Hardware

  • HDD
  • Memoria del sistema (RAM)
  • UPC
  • Memoria de video
  • GPU
  • pantalla LCD

Software

  • Sistema operativo
  • API de DirectX / OpenGL
  • Nvidia Driver

¿Alguien puede explicar el proceso, tal vez con algún tipo de diagrama de flujo para ilustración?

No debería ser una explicación compleja que cubra cada paso (supongo que eso iría más allá del alcance), sino una explicación que un técnico de TI intermedio puede seguir.

Estoy bastante seguro de que muchas personas que incluso se llamarían profesionales de TI no podrían describir este proceso correctamente.

JohnnyFromBF
fuente
¡Su dilema terminaría si pudiera considerar la GPU como una extensión de la CPU!
KawaiKx

Respuestas:

55

Decidí escribir un poco sobre el aspecto de programación y cómo los componentes se comunican entre sí. Tal vez arroje algo de luz sobre ciertas áreas.

La presentación

¿Qué se necesita para tener esa imagen única, 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 coordenadas.) Supongamos que el programa simplemente le dijo a la tubería de procesamiento de gráficos sobre cada vértice individual (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:

// Clear The Screen And The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

// Reset The Current Modelview Matrix
glMatrixMode(GL_MODELVIEW); 
glLoadIdentity();

// Drawing Using Triangles
glBegin(GL_TRIANGLES);

  // Red
  glColor3f(1.0f,0.0f,0.0f);
  // Top Of Triangle (Front)
  glVertex3f( 0.0f, 1.0f, 0.0f);

  // Green
  glColor3f(0.0f,1.0f,0.0f);
  // Left Of Triangle (Front)
  glVertex3f(-1.0f,-1.0f, 1.0f);

  // Blue
  glColor3f(0.0f,0.0f,1.0f);
  // Right Of Triangle (Front)
  glVertex3f( 1.0f,-1.0f, 1.0f);

// Done Drawing
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 se comunique con la tarjeta gráfica (o el controlador, que luego se comunica 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:

ingrese la descripción de la imagen aquí

Si no limpia la pantalla, simplemente dibujará sobre ella cada fotograma. Por eso llamamos glClearcon el GL_COLOR_BUFFER_BITset. 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

ingrese la descripción de la imagen aquí
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 glBegincon la GL_TRIANGLESconstante.
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 glColor3f antes de llamar 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 sea 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 glVertex3fcon otras coordenadas (que se rotan). O podría dejar que la GPU haga todo el trabajo, llamando glRotatefantes de dibujar:

// Rotate The Triangle On The Y axis
glRotatef(amount,0.0f,1.0f,0.0f);               

amountes, por supuesto, solo un valor fijo. Si desea animar , tendrá que realizar un seguimiento amounty aumentarlo cada fotograma.

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

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

glRotateproduce una rotación de anglegrados 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 un tiempo increíblemente largo, 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. Supongo 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.

ingrese la descripción de la imagen aquí
Fuente de imagen

Der Hochstapler
fuente
38

Es difícil entender exactamente qué es lo que no entiendes.

La GPU tiene una serie de registros que asigna el BIOS. Esto permite que la CPU acceda a la memoria de la GPU e indica a la GPU que realice operaciones. La CPU conecta los valores en esos registros para asignar parte de la memoria de la GPU para que la CPU pueda acceder a ella. Luego carga instrucciones en esa memoria. Luego escribe un valor en un registro que le dice a la GPU que ejecute las instrucciones que la CPU cargó en su memoria.

La información consiste en el software que necesita ejecutar la GPU. Este software se incluye con el controlador y luego el controlador maneja la división de responsabilidad entre la CPU y la GPU (ejecutando partes de su código en ambos dispositivos).

Luego, el controlador administra una serie de "ventanas" en la memoria de la GPU que la CPU puede leer y escribir. En general, el patrón de acceso implica que la CPU escriba instrucciones o información en la memoria de la GPU asignada y luego instruya a la GPU, a través de un registro, para que ejecute esas instrucciones o procese esa información. La información incluye lógica de sombreado, texturas, etc.

David Schwartz
fuente
1
Gracias por tu explicación. Básicamente, lo que no entendí es cómo el conjunto de instrucciones de la CPU se comunica con el conjunto de instrucciones de la GPU, pero obviamente es el controlador el que hace esa parte. A eso me refería con capas de abstracción.
JohnnyFromBF
2
No hay un conjunto de instrucciones de CPU involucrado. El controlador y el tiempo de ejecución compilan su CUDA, OpenGL, Direct3D, etc. a programas / núcleos de GPU nativos, que también se cargan en la memoria del dispositivo. El búfer de comandos se refiere a aquellos como cualquier otro recurso.
Axel Gneiting
2
No estoy seguro de a qué programas se refiere (que se ejecutan en la GPU y se incluyen con los controladores). El gpu es en gran medida hardware de función fija, y los únicos programas que ejecutará son sombreadores, que son proporcionados por la aplicación, no por el controlador. El controlador solo compila estos programas y luego los carga en la memoria de la gpu.
Ben Richards
1
@ sidran32: Por ejemplo, en la arquitectura Kepler de nVidia, los núcleos, las secuencias y los eventos son creados por software que se ejecuta en la GPU, no (generalmente) la CPU. El software del lado de la GPU también gestiona RDMA. El controlador carga todo ese software en la memoria de la GPU y funciona como un "mini-SO" en la GPU que maneja el lado GPU del par cooperante CPU / GPU.
David Schwartz
@DavidSchwartz Me olvidé de las tareas de cálculo de GPU. Sin embargo, todavía se comportan de manera similar a los sombreadores, en la implementación, de todos modos. Sin embargo, no lo llamaría un "mini-sistema operativo", ya que no tiene la misma funcionalidad típicamente asociada con los sistemas operativos. Todavía es un software muy especializado, ya que la GPU no está diseñada como una CPU (por una buena razón).
Ben Richards
13

Tenía curiosidad y quería saber todo el proceso desde hacer doble clic en Triangle.exe en Windows XP hasta que pueda ver el triángulo girando en el monitor. ¿Qué sucede, cómo interactúan la CPU (que primero maneja el .exe) y la GPU (que finalmente muestra el triángulo en la pantalla)?

Supongamos que realmente sabe cómo se ejecuta un ejecutable en un sistema operativo y cómo se envía ese ejecutable desde su GPU al monitor, pero no sabe qué está sucediendo en el medio. Entonces, echemos un vistazo desde un aspecto de hardware y extendamos más en la respuesta del aspecto del programador ...

¿Cuál es la interfaz entre CPU y GPU?

Usando un controlador , la CPU puede comunicarse a través de funciones de la placa base como PCI a la tarjeta gráfica y enviar comandos para ejecutar algunas instrucciones de GPU, acceder / actualizar la memoria de GPU , cargar un código para ejecutar en la GPU y más ...

Pero, realmente no puede hablar directamente con el hardware o el controlador desde el código; entonces, esto tendrá que suceder a través de API como OpenGL, Direct3D, CUDA, HLSL, Cg. Mientras que el primero ejecuta las instrucciones de la GPU y / o actualiza la memoria de la GPU, el segundo realmente ejecutará el código en la GPU, ya que son lenguajes físicos / sombreadores.

¿Por qué ejecutar código en la GPU y no en la CPU?

Si bien la CPU es buena para ejecutar nuestros programas diarios de estación de trabajo y servidor, no se pensó mucho en todos esos gráficos brillantes que ves en los juegos de estos días. En aquellos días había renderizadores de software que hicieron el truco de algunas cosas en 2D y 3D, pero eran muy limitantes. Entonces, aquí es donde entró en juego la GPU.

La GPU está optimizada para uno de los cálculos más importantes en gráficos, Matrix Manipulation . Mientras que la CPU tiene que calcular cada multiplicación en una manipulación de matriz una por una (más tarde, cosas como 3DNow! Y SSE se pusieron al día), ¡la GPU puede hacer todas esas multiplicaciones a la vez! Paralelismo.

Pero los cálculos paralelos no son la única razón, otra razón es que la GPU está mucho más cerca de la memoria de video, lo que lo hace mucho más rápido que tener que hacer viajes de ida y vuelta a través de la CPU, etc.

¿Cómo muestran las instrucciones / memoria / código de la GPU gráficos?

Hay una pieza que falta para que todo funcione, necesitamos algo en lo que podamos escribir y que luego podamos leer y enviar a la pantalla. Podemos hacer esto creando un framebuffer . Independientemente de la operación que realice, eventualmente actualizará los píxeles en el framebuffer; que además de la ubicación también contienen información sobre color y profundidad.

Veamos un ejemplo en el que desea dibujar un sprite de sangre (una imagen) en alguna parte; primero, la textura del árbol en sí se carga en la memoria de la GPU, lo que facilita volver a dibujarla a voluntad. Luego, para dibujar el sprite en algún lugar, podemos traducir el sprite usando vértices (colocándolo en la posición correcta), rasterizándolo (convirtiéndolo de un objeto 3D en píxeles) y actualizando el framebuffer. Para tener una mejor idea, aquí hay un diagrama de flujo de tubería OpenGL de Wikipedia:

Esta es la esencia principal de toda la idea gráfica, más investigación es tarea para el lector.

Tamara Wijsman
fuente
7

Para simplificar las cosas, podemos describirlo así. Algunas direcciones de memoria están reservadas (por BIOS y / o sistema operativo) no para RAM sino para la tarjeta de video. Cualquier dato escrito en esos valores (punteros) va a la tarjeta. Entonces, en teoría, cualquier programa puede escribir directamente en la tarjeta de video simplemente conociendo el rango de direcciones y así es exactamente como se hizo en los viejos tiempos. En la práctica con los sistemas operativos modernos, esto es administrado por el controlador de video y / o la biblioteca de gráficos en la parte superior (DirectX, OpenGL, etc.).

ARIZONA.
fuente
1
-1 pregunta cómo puede comunicarse una llamada de DirectX API desde la CPU con la GPU, y su respuesta es "¿es administrado por el controlador y / o DirectX" ? Esto tampoco explica cómo se podría ejecutar el código personalizado (ala CUDA).
BlueRaja - Danny Pflughoeft
3
por favor aprende a leer. Dije escribiendo en direcciones de memoria específicas que están reservadas para GPU en lugar de RAM. Y esto explica cómo puedes ejecutar todo. Se registra un rango de memoria para una tarjeta. Todo lo que escriba en ese rango va a la GPU que ejecuta el procesamiento de vértices, CUDA, lo que sea.
AZ.
5

Las GPU generalmente son impulsadas por memorias intermedias DMA. Es decir, el controlador compila los comandos que recibe del programa espacial del usuario en una secuencia de instrucciones (estado del interruptor, dibujar esto de esa manera, cambiar los contextos, etc.), que luego se copian en la memoria del dispositivo. Luego le indica a la GPU que ejecute este búfer de comando a través de un registro PCI o métodos similares.

Entonces, en cada llamada de sorteo, etc., lo que sucede es que el controlador de espacio del usuario compilará el comando, que luego llama al controlador de espacio del kernel a través de una interrupción y que finalmente envía el búfer de comando a la memoria del dispositivo e indica a la GPU que comience a renderizar.

En las consolas, incluso puedes divertirte haciendo todo eso tú mismo, especialmente en PS3.

Axel Gneiting
fuente
0

Creo que la CPU envía datos de video a GPU a través del bus y luego GPU lo muestra. Por lo tanto, una GPU más rápida puede manejar más datos de la CPU. De esta manera, parte del procesamiento de cpuoffload a GPU. Por lo tanto, obtienes una velocidad más rápida en los juegos.

Es como la RAM donde la CPU almacena cosas para que pueda cargarse y procesarse rápidamente. Ambos hacen que los juegos sean más rápidos.

O la tarjeta de sonido o la tarjeta de red funcionan con el mismo principio, es decir, obtener datos y descargar algunos trabajos de la CPU.

usuario583641
fuente
Esto duplica otra respuesta y no agrega contenido nuevo. No publique una respuesta a menos que realmente tenga algo nuevo que aportar.
DavidPostill
0

Creo que op no está seguro de qué es exactamente lo que la CPU le dice a la tarjeta gráfica que haga y por qué los comandos relacionados con los gráficos (como los comandos opengl o direct3d) no se envían directamente a la GPU.

La CPU solo le dice a la GPU qué renderizar. Todas las instrucciones primero pasan por la CPU donde se configuran / inicializan para que la GPU realmente haga el renderizado.

Dave
fuente