Perl isn't normally the first language you'd think to use for rendering realtime graphics, but it's quite doable on todays's hardware, and is even pretty efficient when combined with OpenGL display lists and XS (C code linked into perl).
First off, I should mention that I'm not using all the modern fancy shaders or buffer objects that OpenGL offers, and just using the original matrix stack and point plotting. While these might be considered slow for video games with polygon counts in the ten-thousands, it's perfectly fine for polygon counts in the hundreds, and shaders would be overkill. It works especially well with the Display List feature, where you plot the vertices of a model once, then replay the model each frame using a single command. This is also particularly convenient when prototyping; just hack around plotting vertices until you get what you want, then add some display list curly braces around as much as you can pre-compile for a nice speed boost.
TODO
(This isn't anywhere close to a proper write-up, but I might eventually get there. I might also upgrade to modern OpenGL shaders etc. before I ever get around to publishing my library of convenience functions.)
Example: Speedometer Needle
This example shows off how display lists can reduce a bunch of Perl OpenGL overhead. This particular example isn't a great usage of display lists (editing the texture would be more sensible) but is easy enough to read that it shows off the concept well.
If you look at my speedometer needle in the demo videos, you can see that it is semi-transparent around the middle, and solid out near the speedometer ticks. The "right way" to do this is by adjusting the alpha channel on the texture, but at the time I was developing it I wasn't sure where I wanted the alpha transition, or how much alpha I wanted, so I just traced out a solid texture in Gimp and rendered it as multiple polygons, with a varying alpha channel blended in.
Here is the code that performs this:
# Draw the needle
localmatrix {
rotate z => -.25+$self->gauge_angle_fn->($mph);
($self->{_needle_displaylist} ||= do {
my $img= $res->img('speed-needle');
$img->gl_tex->bind; # make sure it's loaded before creating display list
displaylist {
$img->gl_tex->bind;
my $hub_rad= $img->height / $img->width * $self->gauge_tick_rad / 2;
triangle_strip {
glColor4d(1,1,0,0.2);
glTexCoord2d(0,0);
glVertex2d(-$hub_rad, -$hub_rad);
glTexCoord2d(0,1);
glVertex2d($hub_rad, -$hub_rad);
glColor4d(1,1,0,0.3);
glTexCoord2d(.5,0);
glVertex2d(-$hub_rad, -$hub_rad+($hub_rad + $self->gauge_tick_rad)*.5);
glTexCoord2d(.5,1);
glVertex2d($hub_rad, -$hub_rad+($hub_rad + $self->gauge_tick_rad)*.5);
glColor4d(1,1,0,1);
glTexCoord2d(1,0);
glVertex2d(-$hub_rad, $self->gauge_tick_rad);
glTexCoord2d(1,1);
glVertex2d($hub_rad, $self->gauge_tick_rad);
};
};
})->exec;
};
The "localmatrix" function is a thing I wrote that forces a glPushMatrix/glPopMatrix around the block of code. It is equivalent to
glPushmatrix();
try {
...
}
finally {
glPopMatrix();
}
The second line rotates the coordinate space according to the current speed. It's
another "sugar" function I wrote that is equivalent to the OpenGL
glRotate(angle*2*PI, 0, 0, 1)
, but which I find more convenient.
It also takes "circle fraction angles", so -.25
means a quarter rotation to
the left. gauge_angle_fn
is a function that scales between miles-per-hour and
the angle it should point for that speed.
The third line is a bit messy, but what it does is to load the value of object field
"_needle_displaylist", and if that doesn't exist, then populate it with the result of the
do {}
block.
To construct the display list, the first line loads the image resource named 'speed-needle'. The resource manager will lazy-load the images the first time they are referenced, but it won't actually load them into an OpenGL texture until the first time it is bound to the OpenGL context, so the next line does that prior to starting the display list. (otherwise the allocating/loading of the texture would become part of the display list, which would be bad)
The displaylist
function is another sugar method I wrote. It calls
glGenLists
, glNewList
, and glEndList
around the
block of code.
The operations inside the display list are to bind the texture (which must be done every frame since only one texture is active at once), then plot out four triangles complete with texture coordinates and colors that include alpha blending. The math involved is mostly to size the polygons so that they reach from the origin of the gauge to the speed tick marks around the edge. The ordering of the vertexes is based on GL_TRIABGLE_STRIP, which you can look up if you're interested.
On completion of the displaylist
block, OpenGL internally stores this list
of gl*
operations in a buffer that it can play back quickly. The return value
of the displaylist
function is a Perl object that wraps the OpenGL display list
(which is an integer) and if that object ever goes out of scope it calls
glFreeList
. Until then, you can call exec()
on that object to
execute a glCallList
, which I do on the final line.
To recap, after the first iteration, the only thing that happens in this code is:
# Draw the needle
localmatrix {
rotate z => -.25+$self->gauge_angle_fn->($mph);
$self->{_needle_displaylist}->exec;
};
and that corresponds to the OpenGL calls of:
glPushMatrix();
glRotated(2*PI*(-.25+$self->gauge_angle_fn->($mph)), 0, 0, 1);
glCallList($self->{_needle_displaylist}->id);
glPopMatrix();