Grupo de Usuarios de Python/pyopengl/Contribuyendo al Desarrollo
De Proyecto Ciencia
Regresar a la Índice Principal
Aquí se describe el proceso necesario para empezar a contribuir con el desarrollo de PyOpenGL 3.X. Destaca la arquitectura básica del sistema y como un nuevo desarrollador deberá trabajar en el. Se asume que el lector tiene suficientes conocimientos de Python, Numpy y ctypes.
Contenido |
La Historia de PyOpenGL 3.X
PyOpenGL 3.X es una re-implementación de las ataduras de OpenGL para Python. Históricamente existían dos otras lineas de implementaciones de OpenGL para Python.
- PyOpenGL hasta el 1.X fue escrito como una extensión de C usando únicamente el API de C para Python. Estaba caracterizado por una API muy intuitiva y "Pythonica" que solo parecía hacer lo que se esperaba que hiciera (en la mayoría de los casos) por que los autores actualmente se sentaron a escribir el código para cada función. Esto hizo que el mantenimiento y su extensión fuera extremadamente difíciles, y el soporte para las extensiones era mínimo o nulo. Este es el código base creado originalmente por David Ascher et. al. Este código fue enteramente sobrepasado por PyOpenGL 2.X.
- PyOpenGL 2.X fue escrito como un conjunto de typemaps por SWIG (envoltorio simplificado y generador de interfaces, también conocido como PyOpenGLSWIG). el código era básicamente un gigantesco conjunto de expansiones tipo macro anidadas, reglas de combinación de patrones SWIG y guiones distutils hackeados fuertemente que automáticamente cambiaban las cabeceras de OpenGL en exenciones útiles de C. PyOpenGL 2.X tenia mucho mejor soporte para las extensiones y mucho mejor cobertura que la serie 1.X, pero era extremadamente difícil modificar o realizar un debug del sistema. Era también notoriamente difícil compilarlo/instalarlo. Este es el código base creado por Tarn Weisner Burton.
Si esta interesado en las razones que llevaron a la reescritura, lea esta información (en ingles), donde se destaca el por que de la re-implementación.
Obteniendo el Código Fuente
PyOpenGL 3.X es desarrollado y mantenido en el sitio de LaunchPad. Para realizar una rama de la versión actual de PyOpenGL:
bzr branch lp:pyopengl bzr branch lp:pyopengl-demo
Necesitara agregar el directorio pyopengl/OpenGL a su PYTHONPATH para trabajar directamente desde la salida (checkout).
Cuando tenga cambios en los que quiera trabajar, puede colocarlos en su cuenta de LaunchPad y pedir una unión (merge) al tronco de PyOpenGL, o generar un conjunto de unión con:
bzr send -o my-patch.diff
Y enviarlo a la lista de correos de PyOpenGL-dev o a Mike Fletcher.
La Arquitectura de PyOpenGL 3.X
Estas son los objetivos del diseño de PyOpenGL 3.X
- Acceso a todas las funcionalidades de OpenGL
- Permitir acceso a las funcionalidades de OpenGL 1.x, 2.x y 3.x
- Acceder a (todas) las extensiones modernas
- Compatibilidad hacia atrás
- No es un requerimiento de fanáticos, pero la mayoría de código de legado debería ejecutarse sin cambios
- Todo código legado debería ejecutarse solo con cambios menores
- Insular al desarrollador de PyOpenGL de las implementaciones (ctypes) para que el código pueda continuar funcionando en múltiples implementaciones
- Facilidad de desarrollo
- El punto de esto es hacer posible que mas gente trabaje en el sistema
- Jugar bien con otros
- Alternar implementaciones de Python (PyPy, IronPython)
- Librerías de Interfaces Gráficas de Usuario (GUI)
- Librerías numéricas
- Comportamiento de hilos
- Librerías multimedia
- Flexibilidad y robustez
- Degradación elegante al haber falta de funcionalidad
- Facilidad para realizar depuración (debugging)
- Reporte de errores informativos
- Facilidad de instalación
- Portabilidad (nota, estamos limitados a las plataformas con ctypes, pero eso deberá crecer mas rápido que lo que nosotros crecemos, dado que ahora ctypes es parte de Python)
- Simplicidad en el proceso de instalación (¡sin distutils hackeados!)
- Uso de eggs y easy_install
- Habilidad de conectar soporte para nuevos data-types y demas
Abstracción de la Plataforma
PyOpenGL esta exponiendo la funcionalidad por "plataforma" (Sistema Operativo y Hardware) al ambiente de Python. Las diferencias entre varias plataformas son abstraídas de tal manera que trasladar PyOpenGL a una nueva plataforma es mas que todo la implementación de un pequeño módulo en el sub-paquete de la "plataforma".
Cada plataforma tiene su propia subclase OpenGL.platform.baseplatform.BasePlatform. Las clases OpenGL.platform.baseplatform.BasePlatform proveen:
- Objetos de librería ctypes para GL, GLU, GLUT y posiblemente GLE. Como definimos estas librerías, los nombres de las librerías, banderas necesarias para cargar y otras cosas mas tienden a ser especificas del sistema.
- Definir los FunctionType de los ctypes usados para llamar funciones en las librerías, ej. que tipo convenciones de llamada usar para llamar a las funciones. En Windows, por lo menos, tenemos que usar windows, en lugar de una convención de llamada C.
- Lsa funciones GetCurrentContext() y CurrentContextIsValid() para permitir que el codigo recupere y/o pruebe si tenemos un contexto actual. Esto es usado para implementar almacenamiento de datos específicos de contexto. Esto es normalmente expuesto por la implementación de OpenGL en la plataforma.
- La función getExtensionProcedure( nombre ) para recuperar una función de extensión de OpenGL por su nombre.
- La función getGLUTFontPointer( constante ) para recuperar un void * (vacio) a una fuente GLUT, diferentes plataformas usan convenciones muy distintas para estos valores.
- Las banderas HAS_DYNAMIC_EXT y EXT_DEFINES_PROTO las cuales le dicen a PyOpenGL 3.X si la plataforma tiene la habilidad para cargar extensiones dinámicas y si usa definiciones de prototipo.
- La función safeGetError() para recuperar el estado de error de OpenGL si hacerlo seria seguro (ej. tenemos un contexto valido o la plataforma puede manejar tener una llamada a glGetError cuando no hay un contexto valido).
- Las funciones/métodos para crear/recuperar apuntadores de funciones a librerías (createBaseFunction y createExtensionFunction), las cuales nos ínsula de los cambios en los ctypes al igual que darnos un lugar conveniente para agregar funcionalidad a una función.
def createBaseFunction( functionName, dll=OpenGL, resultType=ctypes.c_int, argTypes=(), doc = None, argNames = (), ):
y
def createExtensionFunction( functionName, dll=OpenGL, resultType=ctypes.c_int, argTypes=(), doc = None, argNames = (), ):
Nuevas implementaciones de plataformas son registradas en setup.py usando el punto de entrada en pkgtools. Usamos los sys.platform y os.name (en ese orden o preferencia) para decidir cual punto de entrada cargar.
Envoltorios Auto-generados (wrappers)
Existen dos grandes sistemas de envoltorios disponibles para usar con ctypes. Originalmente PyOpenGL 3.X usaba el modulo generador de ctypes (basado en GCC-XML) para producir sus envoltorios. Estamos cambiando para usar el modulo pyglet/tools/wraptypes, pero todavía no hay un generador funcionando, eventualmente cambiaremos a usar wraptypes para ese trabajo también.
Generador de wraptypes (método nuevo, no terminado)
El valor principal de los warptypes es que es fácil de instalar y configurar, y que permite cabeceras de paso (parsing headers) las cuales no son "nativas" en la plataforma donde se están ejecutando. El Generador de envoltorios (gengl.py) no esta tan avanzado/terminado como el modulo openglgenerator.py, pero puede generar módulos específicos de plataforma bastante bien, y el código por debajo esta bajo desarrollo activo con mira a operaciones relacionadas con GL.
Para ejecutar este generador, simplemente haga un retiro (check out) del repositorio SVN de pyglet y agregue el paquete tools/wraptypes a su PYTHONPATH, luego ejecute el modulo src/gengl.py en el arbol fuente de PyOpenGL.
Generador de Envoltorios ctype (metodo viejo, roto)
Los ctypes incluyen un mecanismo basado en GCC-XML el cual genera automáticamente envoltorios para muchas librerías de C. PyOpenGL 3.X usa una versión extendida de este auto-generador (en el subdirectorio src del repositorio CVS, vea generateraw.py y openglgenerator.py) para producir el API "crudo" al estilo C para las librerías centrales. Estos son los módulos en los paquetes OpenGL.raw. Si desea usar ctypes directamente con una API al estilo C es posible importarla directamente y usar estos módulos.
El generador también produce módulos de "anotaciones" en cada uno de los paquetes raw.*. Estos contienen llamadas que envuelve las funciones base en envoltorios con tamaño-en-base-de-arreglos. Las constantes del modulo también son divididas en módulos separados para facilitar la lectura (este modulo luego es importado al modulo principal).
Mucho del API para las librerías principales pueden ser usadas como están desde los paquetes crudos, así que los módulos del paquete principal normalmente importan todos los símbolos de su modulo raw.XXX y ray.XXX.anotations antes de importar desde los módulos que proveen funcionalidades personalizadas.
Este método se ha roto ya que el generador de ctypes ha cambiados desde que producimos el nuestro (extensive diffs).
Generador de Extensiones OpenGL
A diferencia de los módulos principales, los módulos de extensiones son auto-generados usando un hack de bajo nivel que descarga el archivo de cabecera actualizado del registro de extensiones de OpenGL y pasa el archivo con regexes, emparejando las dos definiciones para que pueden generar una sola llamada createExtensionFunctions que tenga todos los data-types e información de nombres para esa función.
Las definición de extensiones en OpenGL en la cabecera del glext.h están divididas en dos partes, un macro que recupera el apuntador a la función y la función misma. Una de las definiciones provee la lista de parámetros, mientras que la otra provee en nombre propiamente capitalizado. Necesitamos ambas para usar la función.
Una consecuencia de este acercamiento es que PyOpenGL 3.X puede mantenerse al día para las extensiones de OpenGL. Producir las versiones crudas para las nuevas extensiones es de todas formas un solo comando.
Los arreglos de salida de glGet() son manejados especialmente por el auto-generador. Produce llamadas cuyas constantes de registros están basados en el tamaño de los arreglos para que la familia de llamadas glGet* pueda regresar el arreglo de tamaño correcto para una constante dada. Lo hace al combinar información desde los documentos de especificación y las especificaciones de tamaño guardadas en el documento glgetsizes.csv en el directorio fuente.
El proceso de auto-generación también intenta copiar la sección de "Sobrevista" (overview) de la especificación para cada módulo en el docstring del módulo, para que los usuarios y programadores puede identificar el propósito del módulo.
Los módulos de extensión están escritos como un solo archivo, con el código para el material de auto-generación colocado sobre una linea de comentario la cual le indica "no editar arriba de esta".
### DO NOT EDIT above the line "END AUTOGENERATED SECTION" below! ... ### END AUTOGENERATED SECTION
### NO EDITAR arriba de la linea "FIN DE SECCIÓN AUTO-GENERADA" abajo" ... ### FIN DE SECCIÓN AUTO-GENERADA
La personalización de las extensiones se hacen después de la sección auto-generada. Esto (el acercamiento de un solo archivo) se hacer mas que todo para reducir el numero de archivos en el proyecto y para hacer mas fácil hackear en una sola extensión.
Se espera y alienta que los usuarios hacken algún módulo de extensión que les importe para hacerlo mas Pythonico y luego contribuir los cambios al proyecto.
Si remueve el comentario auto-generado cuando se realicen nuevas auto-generaciones no procederán al módulo, sin embargo, tenga en cuenta, que las mejoras a las extensiones auto-generadas ocurrirán al tiempo.
Convertidores y Envoltorios
Cuando un método no puede usar los envoltorios ctypes auto-generados como-son, normalmente nos regresamos a la clase OpenGL.wrapper.Wrapper y a los convertidores definidos en el módulo OpenGL.converters. La clase Wrapper provee un grupo de etapas de transformación de argumentos las cuales permiten la composición de la mayoría de las funciones desde un conjunto común de operaciones simples.
Este acercamiento parecerá familiar para aquellos que han visto el código fuente generado por sistemas como SWIG. Ahí usted define un grupo de reglas de pareo las cuales incluyen pedazos de código que están compuestos de funciones C siendo compiladas. En lugar de pareo basado en reglas, usamos especificaciones explicitas.
En algunos casos es mas fácil escribir un envoltorio personalizado que use ctypes crudos. Podemos hacerlo sin problemas simplemente incluyendo el código en el namespace con el nombre apropiado. El "lazy wrapper" o envoltorio perezoso (descrito a continuación) hace esto mas fácil.
Objetos Envoltorios
Los escenarios en las llamadas del Wrapper son las siguientes:
- pyConverters -- acepta (o suprime) los argumentos de Python entrantes
- Usado para convertir un argumento de Python a un tipo que sea compatible con el data-type esperado.
- Puede ser usado para eliminar un argumento de la lista de argumentos al pasar una no función, esto es útil cuando queremos implicar un argumento de la estructura de otro argumento, por ejemplo.
- cConverters -- hala un argumento compatible con C de la lista de argumentos Python.
- Mira todo el conjunto de argumentos de Python y produce una lista de argumentos 1:1 de objetos Python para las llamadas ctypes.
- cResolvers -- Toma los objetos Python compatibles con C y los convierte en data-types de bajo nivel requeridos por las llamadas ctypes.
- llamadas ctypes, (con verificación de errores, los errores son anotados con los argumentos usados durante la llamada).
- storeVlaues -- llamado para guardar los objetos Python compatibles con C, para, por ejemplo, prevenir la acumulación de basura en un arreglo que este en uso.
- returnValues -- determina que resultados son requeridos desde la función envuelta.
De interés particular es le método wrapper.Wrapper.setOutput el cual permite generar una salida de arreglos para una función que usa un diccionario o función pasado en tamaño tupla, para determinar el tamaño apropiado del arreglo. Vea el módulo OpenGL.GL.glget para ejemplos de uso.
Convertidores
El módulo OpenGL.convertes provee un numero de "funciones" de conversión para usar con el objeto envoltorio del módulo de envoltorios. La idea de estas funciones de conversión es producir código reusable listo que describa un idioma común en la función para envolver. Las librerías y extensiones del núcleo luego usaran estos idiomas para simplificar el envoltorio de sus funciones.
Envolturas Perezosas
Generalmente es deseable escribir un pequeño pedazo de código para envoltorio en Python y luego solo llamar a la operación base. Puede usar el módulo OpenGL.lazywapper para lograr esto. Le pasa la operación base como primer argumento cuando llama a la función que es decorada. Ejemplo de uso:
@lazy( glGetInfoLogARB )
def glGetInfoLogARB( baseOperation, obj ):
"""toma el mensaje de error del sombreador/programa como una cadena de Python
regresa una cadena que es sino hay mensaje
"""
length = int(glGetObjectParameterivARB(obj, GL_INFO_LOG_LENGTH_ARB))
if length > 0:
log = ctypes.create_string_buffer(length)
baseOperation(obj, length, None, log)
return log.value.strip('\000') # null-termination
return
