Trabajando con PyOpenGL y VBOs

En estos últimos días he estado trabajando sobre un renderer para un artículo que estoy produciendo para una importante revista de Linux. El renderer debía dibujar un campo de vectores discretizado, con dimensiones variables de 16×16, 32×32, 64×64 y hasta 96×96 (o más, dependiendo de la cantidad de memoria de video disponible).

Cada elemento en el campo de vectores es representado visualmente por un cuadrado (piensa en una baldoza) y una flecha (que representa la dirección). Cada cuadrado se forma con dos triángulos y cada flecha con tres, por lo cual para un campo de vectores de tan solo 16×16 estaríamos dibujando 1.280 triángulos, mientras que para un campo más grande de 64×64 estaríamos trabajando con un total de 20.480 triángulos.


PyOpenGL Logo

Logo de PyOpenGL



Desarrollando en Python y utilizando PyOpenGL 3.0 en Modo Inmediato, rápidamente se podía notar como la API literalmente se ahogaba con el grán volumen de triángulos a dibujar. Es por esto por lo cual decidí reimplementar el código de rendering usando Vertex Buffer Objects (VBO’s) de OpenGL.

La diferencia en desempeño fue increible. Con un mediano trabajo de refactoring logré obtener una aceleración que permitía redibujar la escena mucho más rápido, tanto más que no me alcanza el refresh rate del monitor para dibujar todos los cuadros que son generados por segundo.

Convertir el código de rendering de Modo Inmediato de OpenGL a VBO’s no fue tarea sencilla sin embargo. El problema fundamental consistió en que para trabajar con VBO’s se deben realizar transferencias de porciones de memoria RAM a buffers en memoria de video, por lo cual es necesario realizar un manejo bastante importante de punteros. Manejar punteros no es problema desde un lenguaje como C o C++, pero se puede llegar a complicar cuando se está trabajando en Python.

El primer problema consistía en convertir las listas de vertices y colores de listas Python a arrays contínuos en memoria. Para esto (y mucho a mi pesar) debí utilizar NumPy.array, lo cual solucionó el problema, pero al costo de agregar una nueva dependencia al código: numpy. Lo bueno es que teniendo un NumPy array, resulta sencillo generar los tipos adecuados para que los datos lleguen correctamente a la memoria de video. El siguiente snippet muestra la llamada a la función de copia de memoria:

from OpenGL.GL import *
from OpenGL.arrays import ArrayDatatype as ADT
import numpy
...
array = numpy.array(vertices_and_colors, dtype=numpy.float32)
glBufferData(GL_ARRAY_BUFFER, ADT.arrayByteCount(array), ADT.voidDataPointer(array), GL_STATIC_DRAW)

El segundo problema que tuve consitió en cómo indicarle a PyOpenGL los offsets dentro del buffer. El buffer de memoria que estoy utilizando está cargado tanto con datos de vértices como de colores, por lo cual debía poder especificar dónde empiezan los vértices y dónde empiezan los colores (en offset de bytes) a la API. Si bien esto parecía sencillo utilizando ctypes, se complicó bastante debido a un bug en PyOpenGL 3.0. La solución fué descargar el código de la Beta de PyOpenGL 3.0.1, compilarlo y hacer uso de ésta biblioteca en vez de la versión disponible en los repositorios de Ubuntu y Macports (actualmente ambas presentan este bug).

Una vez actualizada la biblioteca, crear un puntero genérico (void *) resultó trivial, como se muestra a continuación.

from ctypes import c_void_p
...
glColorPointer(3, GL_FLOAT, 0, c_void_p(colors_offset))

Quiero mencionar que cuando escribí en la lista de usuarios de PyOpenGL preguntando por este último problema, quien me respondió que en realidad se trataba de un bug en la biblioteca no fué ni más ni menos que el autor de la misma (!)

This entry was posted in Computación Gráfica, OpenGL, Programacion, Python, Tutoriales. Bookmark the permalink.