Last time we embedded Lua interpreter into a simple OpenGL application and today I'm going to try the same with
I will use the same SDL skeleton, so we can concentrate only on the Python part.
Python skeleton
Embedding Python interpreter is a little more complicated than embedding Lua, so we will actually make it in three distinct steps.
1. Minimal Python
#include <Python.h>
int main(void)
{
Py_Initialize();
FILE *f = fopen("skeleton.py", "r");
PyRun_SimpleFile(f, "skeleton.py");
fclose(f);
Py_Finalize();
}
g++ minimal.cpp -o python_minimal -lpython25
For compiling on Linux just replace -lpython25 with -lpython2.5. The library is named differently
Compile this program with Mingw on Windows and you will experience a crash. Compiling with Microsoft compiler or compiling on Linux works correctly. What's going on? After some searching, I found the answer in Blender source, which pointed me to the main page of Python embedding documentation, where it explicitly says, that the file descriptors passed to PyRun_*File function, have to be created by the same runtime libraries. Since I'm using Mingw, I had to avoid all the PyRun_*File functions and pass the Python script to the interpreter as a string.
2. Minimal Python as a String
#include <Python.h>
int main(int argc, char*argv[])
{
Py_Initialize();
FILE *f = fopen("skeleton.py", "r");
fseek(f, 0, SEEK_END);
size_t length = ftell(f);
fseek(f, 0, SEEK_SET);
char *data = (char *)malloc(length + 1);
fread(data, 1, length, f);
data[length] = '\0';
fclose(f);
PyRun_SimpleString(data);
Py_Finalize();
return 0;
}
All this code does is opening a file, reading the contents and passing it to Python interpreter, nothing really spectacular. You can place any Python code into skeleton.py such as:
print 'Hello from Python'
3. Calling C Functions from Python
To be able to call a C function from Python you have to do a little more than in case of Lua. You have to create a new module for a set of functions with Py_InitModule. In subsequent Python code you have to import from this module.
#include <Python.h>
// our function, which we call from Python
PyObject * redraw(PyObject *self, PyObject *args)
{
double theta;
PyArg_ParseTuple(args, "d", &theta);
printf("%g\n", theta);
return Py_BuildValue("");
}
// list of methods, which will put into module Foo
static PyMethodDef methods[] =
{
{ "redraw", redraw, METH_VARARGS, "updates the screen" },
{NULL}
};
int main(int argc, char*argv[])
{
Py_Initialize();
FILE *f = fopen("mixed.py", "r");
fseek(f, 0, SEEK_END);
size_t length = ftell(f);
fseek(f, 0, SEEK_SET);
char *data = (char *)malloc(length+1);
fread(data, 1, length, f);
data[length] = '\0';
fclose(f);
// init our module
PyObject *module = Py_InitModule("Foo", methods);
PyRun_SimpleString(data);
Py_Finalize();
return 0;
}
When run in conjunction with this little Python script, it will print out all the rotations of the triangle, which will we display in SDL version later.
from Foo import * i = 1 while (i < 2000): redraw(i) i += 0.5
Skeletons mixed
And now we need just to merge in the SDL code together and we're done
#include <Python.h>
#include <SDL/SDL.h>
#include <windows.h>
#include <gl/gl.h>
PyObject * redraw(PyObject *self, PyObject *args)
{
double theta;
PyArg_ParseTuple(args, "d", &theta);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f,0.0f,0.0f);
glRotatef(theta, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex2f(0.87f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex2f(-0.87f, -0.5f);
glEnd();
SDL_GL_SwapBuffers();
return Py_BuildValue("");
}
static PyMethodDef methods[] =
{
{ "redraw", redraw, METH_VARARGS, "updates the screen" },
{NULL}
};
int main(int argc, char*argv[])
{
atexit(SDL_Quit);
if( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
fprintf(stderr,"Couldn't initialize SDL: %s\n", SDL_GetError());
exit(1);
}
SDL_Surface *screen = SDL_SetVideoMode(400, 400, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_OPENGL);
glViewport(0, 0, 400, 400);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
glMatrixMode(GL_PROJECTION);
glMatrixMode(GL_MODELVIEW);
Py_Initialize();
FILE *f = fopen("mixed.py", "r");
fseek(f, 0, SEEK_END);
size_t length = ftell(f);
fseek(f, 0, SEEK_SET);
char *data = (char *)malloc(length+1);
fread(data, 1, length, f);
data[length] = '\0';
fclose(f);
PyObject *module = Py_InitModule("Foo", methods);
PyRun_SimpleString(data);
Py_Finalize();
return 0;
}
The result, we're getting, is of course the same as last time

In this case as well I couldn't find any statistically significant speed difference between native C and interpreted Python.
All sources and binaries can be downloaded here 4 MB
Very interesting side note: when implementing this example I found out an extremely awesome thing. New versions of Mingw allow linking directly to DLLs. You don't need no .a, .la or .lib files. You can just direct the compiler/linker to the directory with the DLL you're referencing and it will automagically resolve the symbols from within the DLL - simply AWESOME!!!
0 comments:
Post a Comment