Aprovechando el multihilo entre el bucle del juego y openGL


10

Hablar en el contexto de un juego basado en el renderizador de OpenGL:

Supongamos que hay dos hilos:

  1. Actualiza la lógica y física del juego, etc. para los objetos del juego.

  2. Realiza llamadas de sorteo de OpenGL para cada objeto del juego según los datos de los objetos del juego (ese hilo 1 se actualiza constantemente)

A menos que tenga dos copias de cada objeto del juego en el estado actual del juego, tendrá que pausar el Hilo 1 mientras que el Hilo 2 realiza las llamadas de sorteo; de lo contrario, los objetos del juego se actualizarán en medio de una llamada de sorteo para ese objeto, que es indeseable!

Pero detener el subproceso 1 para realizar llamadas de extracción con seguridad desde el subproceso 2 mata todo el propósito de subprocesamiento múltiple / concurrencia

¿Existe un mejor enfoque para esto que no sea usar cientos o miles o sincronizar objetos / cercas para que la arquitectura multinúcleo pueda ser explotada para el rendimiento?

Sé que todavía puedo usar subprocesos múltiples para cargar texturas y compilar sombreadores para los objetos que aún no forman parte del estado actual del juego, pero ¿cómo lo hago para los objetos activos / visibles sin causar conflictos con el dibujo y la actualización?

¿Qué sucede si uso un bloqueo de sincronización separado en cada objeto del juego? ¡De esta forma, cualquier subproceso solo se bloquearía en un objeto (caso ideal) en lugar de en todo el ciclo de actualización / dibujo! Pero, ¿qué tan costoso es bloquear las cerraduras de cada objeto (el juego puede tener mil objetos)?


1. Asumiendo que solo habrá dos hilos no se escala bien, 4 núcleos es muy común y solo aumentará. 2. Respuesta de la estrategia de mejores prácticas: aplique la segregación de responsabilidad de consulta de comandos y el modelo de actor / sistema de entidad componente. 3. Respuesta realista: simplemente piratee la forma tradicional de C ++ con bloqueos y demás.
Den

Un almacén de datos común, una cerradura.
Justin

@justin tal como lo dije con un bloqueo de sincronización La única ventaja del subprocesamiento múltiple será brutalmente asesinado a la luz del día, el hilo de actualización tendría que esperar hasta que el hilo de sorteo llame a todos los objetos, luego el hilo de sorteo esperará hasta que el ciclo de actualización complete la actualización. ! que es peor que el enfoque de un solo subproceso
Allahjane

1
Parece que el enfoque multiproceso en los juegos con API de gráficos asíncronos (por ejemplo, openGL) sigue siendo un tema activo y no hay una solución estándar o casi perfecta para esto
Allahjane

1
El almacén de datos que es común a su motor y su procesador no deberían ser sus objetos de juego. Debe ser alguna representación de que el procesador puede procesar lo más rápido posible.
Justin

Respuestas:


13

El enfoque que ha descrito, usando bloqueos, sería muy ineficiente y probablemente más lento que usar un solo hilo. El otro enfoque de mantener copias de datos en cada hilo probablemente funcionaría bien "en cuanto a velocidad", pero con un costo de memoria prohibitivo y complejidad de código para mantener las copias sincronizadas.

Hay varios enfoques alternativos para esto, una solución popular para el procesamiento de subprocesos múltiples es mediante el uso de un doble búfer de comandos. Esto consiste en ejecutar el back-end del renderizador en un hilo separado, donde se realizan todas las llamadas de dibujo y la comunicación con la API de representación. El subproceso de front-end que ejecuta la lógica del juego se comunica con el procesador de back-end a través de un búfer de comandos (doble búfer). Con esta configuración, solo tiene un punto de sincronización al finalizar un fotograma. Mientras que el front-end está llenando un búfer con comandos de renderizado, el back-end está consumiendo el otro. Si ambos hilos están bien equilibrados, ninguno debería morir de hambre. Sin embargo, este enfoque es subóptimo, ya que introduce latencia en los cuadros renderizados, además, es probable que el controlador OpenGL ya lo esté haciendo en su propio proceso, por lo que las ganancias de rendimiento tendrían que medirse cuidadosamente. También solo usa dos núcleos, en el mejor de los casos. Sin embargo, este enfoque se ha utilizado en varios juegos exitosos, como Doom 3 y Quake 3

Los enfoques más escalables que hacen un mejor uso de las CPU de varios núcleos son los que se basan en tareas independientes , donde se activa una solicitud asincrónica que se atiende en un subproceso secundario, mientras que el subproceso que activó la solicitud continúa con algún otro trabajo. La tarea idealmente no debería tener dependencias con los otros subprocesos, para evitar bloqueos (¡también evitar datos compartidos / globales como la peste!). Las arquitecturas basadas en tareas son más utilizables en partes localizadas de un juego, como animaciones informáticas, búsqueda de IA, generación de procedimientos, carga dinámica de accesorios de escena, etc. Los juegos están naturalmente llenos de eventos, la mayoría de los eventos son asíncronos, por lo que Es fácil hacer que se ejecuten en hilos separados.

Finalmente, recomiendo leer:


¡Sólo curioso! ¿Cómo sabes que Doom 3 usó este enfoque? ¡Pensé que los desarrolladores nunca dejaban salir las técnicas!
Allahjane

44
@Allahjane, Doom 3 es de código abierto . En el enlace que he proporcionado, encontrarás una revisión de la arquitectura general del juego. Y se equivoca, sí, es raro encontrar juegos de código abierto completo, pero los desarrolladores suelen exponer sus técnicas y trucos en blogs, documentos y eventos como GDC .
glampert

1
Hmm ¡Esta fue una lectura increíble! gracias por hacerme consciente de esto!
Obtienes
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.