Programación OpenGL: Pixel Buffer Objects

Hace mucho que no posteaba nada en la sección de programación (de hecho la tengo muy abandonada desde que empecé el blog…) así que siguiendo el post-frenzy de hoy, voy a comentar un par de cosas sobre los Pixel Buffer Objects. Es un tema medianamente avanzado de programación OpenGL, sobre el que hay muy poca documentacion bien explicada, por eso me he animado a escribir estas líneas.

Los buffers objects en OpenGL ya están muy bien asentados. en particular, los Vertex Buffer Objects (VBO) son usados ampliamente por su ganancia en rendimiento. Esta ganancia se debe a que en lugar de almacenar en la memoria del sistema los vértices, índices, coordenadas de textura, etc… Pedimos al driver OpenGL que nos dé un puntero a memoria de video (o lo que a él le parezca más conveniente) para guardarlos ahí. Con ello, cuando toque dibujar un objeto, en lugar de tener que subir los vértices y tal a la tarjeta gráfica, ésta los tendrá en su propia memoria, eliminando una costosa copia y acelerando el rendering notablemente.

Ahora bien, ¿y los Pixel Buffer Objects (PBO) ? Pues bien, la mecánica es similar, pero con una diferencia que no explican demasiado bien en ningún sitio: Las texturas, dada su naturaleza, son internas al driver OpenGL, es decir, que el driver no nos puede dar un puntero a la textura (entre otros motivos porque a priori no sabemos el formato en el que se guardan internamente). ¿Entonces, cómo lo hacemos? Pues sencillo: Según el flujo habitual, en OpenGL, al modificar una textura se llama a glTexSubImage2D con un puntero con los datos nuevos de la textura. El driver se copia internamente esos datos y la gráfica actualiza de esos datos su representación interna de la textura. Esto no acarrea demasiado problema cuando la textura la declaramos una vez y ya para toda la aplicación, pero cuando estamos por ejemplo haciendo streaming de video, estamos realizando una copia continuamente que podríamos evitar. Para ello usamos los buffer objects en este caso. El mecanismo sería el siguiente:

/////INICIALIZACIÓN DEL BUFFER//////

//Antes de empezar, nos aseguramos de que no tenemos ningún buffer asignado
glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB,0);
//Ahora, generamos un buffer
glGenBuffers(1,&bufferID);
//Lo activamos
glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bufferID);
//Y configuramos el buffer según nuestra necesidad (ver más abajo)
glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, texSize, NULL, GL_STREAM_DRAW);
//Y volvemos a desasignar el buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB,0);

Aquí ya tenemos nuestro buffer configurado para su futuro uso. Supongamos que tenemos una textura ya creada, con el identificador almacenado en texID. Entonces, para actualizarla, hacemos:

//Activamos la textura que queramos actualizar
glBindTexture(GL_TEXTURE_2D,texID);
//Activamos el buffer que habíamos creado antes
glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bufferID);
//Le pedimos al driver un puntero a la memoria del buffer
void * pboMem = glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY);

//Usar el puntero pboMem para modificar la textura
[…]

//Al acabar, devolver el puntero
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB);
//Y actualizar la textura desde el buffer que acabamos de rellenar
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL_BGRA, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
//Y por último, desasignar el buffer.
glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB,0);

Lo que estamos haciendo es crear un buffer en la memoria de la gráfica (o lo más cerca posible de donde la textura está realmente almacenada), poner ahí nuestra nueva imagen, y decirle al driver que la actualice desde ahí, eliminando la copia intermedia que hacíamos antes. (Sigue siendo necesaria una copia, desde el buffer hasta donde la textura realmente reside, pero esta es la manera más rápida de hacerlo, ya que la tarjeta puede ejecutar un DMA sobre esa memoria).

Cada vez que activamos un buffer con la opción GL_PIXEL_UNPACK_BUFFER_ARB, le estamos diciendo al driver que desde ese momento, y hasta que lo desactivemos (activando el buffer 0), todas las instrucciónes que manejen pixels cogerán como fuente el buffer y tratarán al puntero que le pasamos como un offset dentro de ese buffer, de ahí la macro BUFFER_OFFSET(n) que por si no la tenéis definida es:

#define BUFFER_OFFSET(i) ((char*)NULL + (i))

Podríamos por ejemplo, si recibimos un streaming de video, usar el puntero que nos ha dado el driver para descomprimir en el cada fotograma, y evitar completamente el trasiego de buffers, en la práctica multiplicando prácticamente por dos la velocidad de actualización de la textura. Para este caso la opción GL_STREAM_DRAW informa al driver de que vamos a escribir una vez en el buffer y dibujar de el unas cuantas (típico de un fotograma de video), y GL_WRITE_ONLY indica que sólo pretendemos escribir al buffer. Esto permite al driver optimizar la memoria que va a usar para su óptimo rendimiento.

Otro detalle sobre rendimiento: hay que intentar en la medida de lo posible utilizar texturas en BGRA, ya que el GDI de Windows usa sólo BGRA, y si especificamos otro formato, durante la creación de la textura el driver tendrá que convertir el formato, degradando notablemente el rendimiento. Esto para la creación de la textura al principio en el programa no supone un problema, pero si vamos a estar constantemente actualizando la textura sí supone una degradación importante del rendimiento. Otro formato que podemos utilizar es GL_LUMINANCE en el caso de texturas en escala de grises, ya que al sólo tener un canal mejora el rendimiento porque el driver internamente expandirá el canal a BGRA (no hay reordenación, con lo que los beneficios del ahorro de ancho de banda (4 veces menos) merecen realmente la pena). Si vas a leer de la textura lo más rápido es leer BGRA, ya que cualquier otro modo (incluído GL_LUMINANCE) supone una sobrecarga adicional.

Para más información, acude al registro de extensiones OpenGL, en la que te explican cada parámetro y su función.

Bueno, espero haberlo dejado más o menos claro. Se aceptan comentarios🙂

~ por Egon en 22 agosto, 2007.

14 comentarios to “Programación OpenGL: Pixel Buffer Objects”

  1. Interesante! lástima que no sepa nada de OpenGL

  2. Nunca es tarde para aprender😉

  3. esta buena la explicacion pero seria ideal un ejemplo

  4. …pero si tienes el código en C++ del ejemplo… qué más quieres?

  5. Bueno,tengo una duda.¿Podría emplear un buffer para crear en él toda la “escena 3D” con OGL?,mi idea es crear un programa que muestre imágenes reales de un video(ya lo tengo hecho) o de OGL mediante una misma variable compartida o buffer

  6. Sí que puedes, pero hay que hacer un par de pasos más, no puedes usar un Pixel Buffer Object directamente. Lo que tú buscas es un “Frame Buffer Object” Con ello, puedes cambiar el destino de todas las instrucciones de render a un buffer, y con el hacer lo que quieras, desde guardarlo a disco a usarlo como textura. De todas maneras, si lo único que necesitas es volcar el contenido de una ventana opengl a un buffer, y tus requerimientos de tiempo no son duros, puedes renderizar normalmente y con glReadPixels leer de la pantalla.
    Mírate de todas maneras esto:
    http://download.nvidia.com/developer/presentations/2005/GDC/OpenGL_Day/OpenGL_FrameBuffer_Object.pdf
    Es un paper de Nvidia sobre el tema, ellos promovieron EXT_FRAMEBUFFEROBJECT, no sé si habrá llegado ya al ARB.
    Un saludo!

  7. Holas,
    Estoy currando con opengl y me ha venido coj.. bueno ya sabes, muy bien tu minitutorial. ¿sabes si funcionaría todo igual al usar Shaders?Un saludo

  8. Claro, no tiene por qué no ir, de hecho forma parte de una especificación común!😀

  9. Holas de nuevo,
    Te comento mi proyecto, porque me estoy perdiendo ultimamente. Tengo un streaming de video en formato yuv que, dividiendo cada frame en 3 texturas y aplicandole una transformacion con un frame shader, paso a RGB. El caso es que ademas la imagen va sobre una malla de triangulos (al final terminará mostarndose en video 3D el stream) y todo con Buffer Objects.
    Ahora cómo lo resuelvo; en init creo los 2 buffers para coordenadas y uno para vertices, y en cada display; primero las 3 texturas, luego paso los buffers y termino activando cada uno de los GL_TEXTUREi. Crees que es una buena solución o piensas que iría mejor con los pixel buffers?? Alguna sugerencia?
    Un saludo y gracias de antemano

  10. A ver, con pixel buffers iría mejor, pero lo que te tienes que plantear es si necesitas optimizar hasta ese nivel el rendimiento. Yo soy muy amigo de las optimizaciones, incluso trabajo en ello profesionalmente, y por ello mismo te digo que son necesarias cuando realmente son necesarias. Es posible que sea más costoso computacionalmente conseguir el fotograma (descomprimirlo o pedírselo al hardware) que el resto en OpenGL. Analiza si tienes ahí un cuello de botella que necesitas solucionar, y si es así, yo te propondría (si he entendido bien tu problema): crea un VBO (vertex buffer object) interleaved para geometría y coordenadas de textura para cada punto de la malla, y otro VBO con los índices para generar los triángulos correspondientes. Luego, crea dos pixel buffer object, y en el primero pon la imagen en YUV, sin separar los canales (no se me ocurre por qué necesitas separarlos) y mediante el shader, en el segundo buffer generas la textura en RGB. Lo siguiente sería activar la textura y renderizar el VBO con una llamada a DrawElements. Y desde luego, desincroniza la tarea de generar la textura RGB de renderizar, es decir, no generes la textura RGB cada fotograma renderizado, sino sólo cuando llegue un nuevo fotograma YUV. Y en función del tipo de aplicación que estés haciendo y la plataforma, considera hacerlo en la CPU, porque ensuciar la caché de la gráfica tampoco es bueno, pero eso lo dejo a tus benchmarks.
    Un saludo!

  11. De hecho, ahora que lo pienso, ¿neceistas la textura RGB para algo más que renderizarla? Porque si no es así, no hace ni falta que la crees como PBO…

  12. hola estoy haciendo un programa en visual c++6.0 y opengl estoy utilizando las librerias de opengl lo que necesito saber es como puedo visualizar dos imagenes dentro de una misma ventana de opengl hasta ahorita nada mas e logrado visualizar una imagen pero necesito visualizar dos imagenes no se si alguien me podria ayudar se los agradeceria mucho

  13. A ver, hay muchas maneras de hacerlo, la más simple es utilizar una textura para cada imagen, y dibujar dos cuadrados, uno con cada textura.

  14. un ejemplo podrias publicar

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

 
A %d blogueros les gusta esto: