Para el Vaporizador de Buffer Vertex, múltiples glBufferSubData VS Orphaning?

8

Estaba aprendiendo OpenGL recientemente. En los juegos, necesitamos actualizar la posición de los objetos del juego con frecuencia, y entrarán y saldrán de la pantalla constantemente. Por lo tanto, significa que al renderizar también necesitamos actualizar el búfer de vértices con bastante frecuencia.

En el contexto de OpenGL, una forma intuitiva es usar glBufferSubData para actualizar los que han cambiado.

Pero también leí en línea un truco llamado Orphaning que crea una nueva información de búfer y le subo toda la información de vértices.

También debido al costo de los cambios de estado y el costo de carga, múltiples glBufferSubData también pueden costar más.

Aquí está mi pregunta

  1. ¿Qué método es mejor?
  2. ¿Los puestos realmente importan en este caso?
  3. ¿Los cambios de estado y el costo de carga realmente son importantes en este caso?

¡Gracias!

YiFeng
fuente
¿Por qué desea volver a cargar la geometría cuando en realidad solo puede actualizar la matriz? en otras palabras, no necesita volver a cargar los vértices cuando solo las transformaciones deben cambiar.
concept3d
@ concept3d Sí, tienes razón! Cometí un error al escribir las descripciones del problema. ¡Gracias por recordarme eso! Ya actualicé las descripciones.
YiFeng

Respuestas:

9

Esta es una pregunta compleja con muchos pequeños detalles que realmente importan, el rendimiento variará según la plataforma y la aplicación. Por lo tanto, debe hacer un perfil de posibles cuellos de botella antes de invertir en optimizaciones.

Dicho esto, en primer lugar, supongo que debe reducir las cargas y actualizaciones tanto como pueda, por ejemplo, usar instancias.

En segundo lugar, tenga en cuenta que las GPU no pueden transferir buffers y renderizar al mismo tiempo, por lo que el dispositivo procesa secuencialmente todos los comandos de OpenGL en la cola de comandos. Existen diferentes formas de copiar datos y / o hacer que estén disponibles para que la GPU los use.

Hay varias formas de transmitir datos a la GPU

1- glBufferDatao glBufferSubDatamétodo

Usar glBufferDatao glBufferSubDataes como memcpy. se pasa un puntero y se puede realizar una operación DMA , dije que podría porque la memoria podría estar anclada en la memoria de la CPU y utilizada directamente por la GPU sin que realmente se produzca una transferencia de memoria a la GPU, dependiendo del indicador de uso (GL_STREAM). En opinión, deberías probar esto al principio porque es más sencillo de implementar.

2- obtener un puntero a la memoria interna usando glMapBuffer

Si lo anterior no es lo suficientemente bueno que puede usar glMapBuffer, obtiene un puntero a la memoria interna, y puede usar este puntero para llenar el búfer directamente, esto es bueno con las operaciones de lectura y escritura de archivos, ya que puede asignar directamente los datos del archivo a la memoria de la GPU en lugar de copiar primero a un búfer temporal. Si no desea mapear todo el búfer, puede usarlo glMapBufferRangepara mapear una parte del búfer.

Un truco es crear un búfer grande, usar la primera mitad para renderizar y la segunda mitad para actualizar.

3- Huérfano de búfer

Con respecto a la huérfana del búfer, esto se puede hacer usando glBufferData con nulo y los mismos parámetros que tenía. El controlador devolverá el bloque de memoria una vez que no se use. Y será utilizado por la próxima llamada glBufferData (no se asignará memoria nueva).

Todos los métodos mencionados causan una gran cantidad de sincronización costosa, nuevamente las GPU no pueden transferir buffers y renderizar al mismo tiempo.

4- Unsynchronized Buffers

El método más rápido (y el más difícil de acertar) es usar buffers sin sincronización con los que puede usar el GL_MAP_UNSYNCHRONIZED_BITindicador glMapBufferRange, el problema es que no se realiza la sincronización, por lo que podríamos cargar datos en un buffer comenzar a usar, y por lo tanto arruinar todo. Puede usar múltiples buffers con bit de sincronización para facilitar un poco las cosas.

concepto3d
fuente
0

Lo hago de otra manera. Puede haber algo mal allí. Algunas cosas peligrosas ocultas.

(Yo uso C # + OpenTK.GameWindow)

Para la creación de instancias del objeto, uso un Array Buffer Object separado para el conjunto de matrices modelo para cada instancia. En el vértice compartido:

#version 400
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texcoord;
layout (location = 4) in mat4 model_matrix;

uniform mat4 proj_matrix, view_matrix;

En mi código C #, las matrices se almacenan en una matriz flotante float[] mat4_array

Luego, ato la matriz al Array Buffer Object:

public void bind_data_instances()
{
    GL.BindBuffer(BufferTarget.ArrayBuffer, id_InstanceBO);
    GL.BufferData(BufferTarget.ArrayBuffer, mat4_array.Length * sizeof(float), mat4_array, BufferUsageHint.DynamicDraw);
}

Cada cuadro se actualizan las matrices del modelo. Para actualizar mat4_arraysolo llamo:

Buffer.BlockCopy(mat4_array_new, 0, mat4_array, 0, sizeof(float) * mat4_models.Length);

y renderizar la escena. utilizando GL.DrawElementsInstanced.

No hay llamadas adicionales de OpenGL y funciona perfectamente.

Dennis
fuente
¿Cuál es el propósito de tener la matriz modelo como atributo de vértice?
Anton Duzenko
Es para el objeto de instancia dibujar. Y es más rápido que una matriz pasada como una variable uniforme.
Dennis