First of all, it is good that you appear to be on the right track with using VBOs! In general, it is a good idea when working with the GPU to pass as much data to the GPU at once as is possible. This means minimizing the number of draw calls (i.e. send a whole bunch of cubes in a single draw call rather than drawing one cube at a time with
glDrawArrays or worse,
Let's say that the vertices, texture coordinates, normals, etc are all in a massive interleaved VBO or set of VBOs (e.g. one for vertices, one for normals, and so on). How do you rotate these cubes? One approach would be to pass uniforms in for your model, view, and projection matrices for each cube and draw each cube in your VBO with a single glDrawArrays call. This, however, would be exceedingly slow, and it would be better if we could tell the GPU to draw all of our cubes in one go. While there are other techniques to consider in OpenGL such as instancing, one approach could be to pass in transformation matrices as an additional attribute per-vertex. These matrices could either be calculated on the CPU side (possible, but potentially slow, especially in python) or on the GPU side (fast and what I would generally recommend, but possibly wasteful of GPU resources to calculate these transformations repeatedly for each vertex). You can also consider transforming the vertices on the CPU side every frame and passing the transformed vertices to your shader instead, but this could once again be a bottleneck in python.
In addition to the concerns on the OpenGL/GPU side of things, there are also some unique challenges posed by using python. A useful blog post for me that points out some of these issues can be found here, but I will try to sum up some of the highlights.
- There is first the issue of Python's memory model. In python, everything, even primitives such as integers, are all bulky objects built from a basic
PyObject struct rather than direct, native machine types. This can make accessing the data of the underlying objects slow as a result, with even apparently simple operations such as adding two numbers invoking lots of operations behind the scenes in the python interpreter. For fast math operations, this is not conducive to good performance, and there is generally little to gain from the safety features (such as avoiding overflow) that are offered from this additional layer.
- The issues of dealing with objects rather than primitives is worsened when you are working with lots of these objects (aka in many matrices and vectors). You mention in the comments that these matrices that you build are actually python lists. While these lists in python are generally contiguous in memory, they contain contiguous PyObjects representing floats rather than just the floats themselves. This can result in a loss of potential data locality. The result is that accessing each element in your matrices would be slower as well.
- Finally, the performance of loops and function calls is significantly slower in python than it is in say, C. Games generally have a soft realtime requirement for performance, meaning that we generally want them to run interactively at a smooth framerate. If you are targeting 60 fps, for example, this means that you only have about 16.67 milliseconds to execute everything your game needs to do to render a given frame. On my laptop, this amounts to having 100,000 function calls at the most every frame. This can be limiting, especially in tight loops at the core of a game engine. This is frustrating when compared to the overhead of C functions, which are an order of magnitude or two faster to run. This overhead is signficant even when the python calls wrap C calls to system libraries like OpenGL! When possible, these expensive loops should be moved out of python and into faster, typed, and compiled code. The general idea is that you should pass in a group of objects from python to some C function that does all of the hard work!
Hopefully this gives a good idea of some of the challenges that come with using python. I do believe that it should be possible to create great games in python and that scripting languages like python have their place, but there are many factors to consider regarding performance. The choice you make ultimately depends on your workload and what exactly you are rendering to your screen. Let me know if there is anything that I can clear up!