Creating the Graphics of the DeLorean Digital Dashboard
Michael Conrad
mike@nrdvana.net
http://nrdvana.net/
CPAN: NERDVANA
Recap
C++ too time consuming
Experiment with Inline::CPP
Rewrote in Perl on a whim
Worked great
Told story, but no code
Started packaging up
2011-2014 - Graphics in C++
2014 - Rewrite in Perl
2015 - Presented Story at YAPC
New Modules
Most useful stuff shared now
X11/GLX cause no window manager
Sandbox wraps misc utilities
This is deeper dive
X11::Xlib
X11::GLX
OpenGL::Sandbox
But more Practically...
Even though talk about graphics,
unlikely useful
(unless dashboard
,secret aspirations game, and simple)
Will focus on prototyping
and high-performance perl
Rapid Prototyping
High-Performance Perl
before code
want to talk about building blocks
Building Blocks
Legos
These are Legos.
convenient versatile useful
as a kid
Lego is a registered trademark of The Lego Group
Erector
This is erector
made of metal
assemble screws
annoying long to assemble
fiddling screws, hold in place
nice metal smell
rock solid
And this matters some times.
The Erector brand is owned by Spin Master Inc.
Science Olympiad
high school
county-wide
Science Olympiad.
event, build vehicle, simple machanics. spring falling weight
one year, travel then launch pingpong
I volunteered build
decided Legos
target worth most points
focused launching aiming.
no picture, terrible hoarder
this revised version
last 20 years.
original extra stuff ping-pong balls
plan propelling roll down a ramp
testing last minute
not enough speed, size ramp
friction lego wheels
The lego axles weren't strong / rubber bands
push by hand, lose points
last-minute strategy / launch down ramp
live at the event, attach rubber band
, pulled back, let go, exploded
frantic re-assemble
not in time
time negative, maximum negative score
Looking back
fatal mistake / Legos
potential mechanical energy
impractical for Legos
maybe possible
higher chance if used Erector
spent 4x time
Image from www.hammacher.com
Lesson:
Use the right tool for the job
Software
good parallel to sw
High-level rapid / complex programs quickly,
certain tasks explode
horrible disaster
Low-level extreme efficient
take forever complicated
High Level
Rapid Development
Limited Performance
Low Level
Extreme Efficiency
Complex => DNF
Use the Right Tool
early phase dashboard story
counterexample to the science olympiad story
I assumed / need low-level / graphics, wrote it in C.
4 years "the right way" with "the right language",
didn't finish.
Leads to next point,
Dashboard story would indicate otherwise
Tried "right way", but didn't have time
Lesson:
There's no point doing it "right" if you never finish.
Middle Gound?
trapped, supposed to do?
fastest way sw written
assemble modules
packaged up others / Lego bricks
unit tests most of your debugging,
simplified interfaces
plug together
Use efficient foundation blocks
Build upper layers with glue
Don't expect foundation to reach to the top
Don't expect glue to be a foundation
Things to Glue with Perl
Perl, most high-level
link to libraries / efficient languages
problem is library in C horrible
can't just drop to C
while rapid prototyping Perl
but actually, can, Inline
solves exactly that problem!
(apologies Will Braswell; rPerl
said I was going to give it a try
still haven't made time
For more details,
can attend his talk.
Inline::C Inline::CPP magic sauce
discovered in 2014
convinced me to
graphics try in Perl
as previous talk,
worked flying colors,
got prototype
running in just
few months after to perl
use Inline C => '
double add_doubles(double d1, double d2) {
return d1 * d2;
}
';
The end. Auto-compiles and caches.
main purpose presentation
and OpenGL::Sandbox
show off techniques Perl with C.
But, interrupt self again
opengl disclaimer
Perl and C(++)
Disclaimer...
Classic OpenGL API
Modern OpenGL API
OpenGL, Classic (1.x)
old OpenGL API
refer 1.x
based idea 3D coordinte system,
alter with translations rotate or scale
plot points in 3D
describe flat polygon surfaces
enable or disable
color or lighting or texturing
control how polygons painted
define polygons one at a time
sequence - build frame of animation.
swap to screen, start over
next frame animation
Since slow, ask OpenGL compile sequences
"Display List", stored effeciently
probably in graphics memory
play back on demand.
Plot out geometry with function calls
Maneuver through space by changing coordinate systems
Control the pixel math with provided options
OpenGL, Modern (2.x-4.x)
Later, new API (2.x - 4.x)
where allocate buffers hold geometry
ship to card / ask render everything instructions
provide custom math
coordinate spaces / color fill polygons.
No more "plotting", Pack geometry into buffers
No more "maneuvering", pack coordinate spaces into buffers
API requires - plan out geometry ahead of time
and if animate - which vertices or cs in which buffer
planning takes time
learning API more time
(I might be biased, learned old first)
and so we come back to:
Plan the geometry
Plan the storage
Learn the shader process
Learn the shader language
Time
In particular, *don't* need render many
or goal is 2D - textures
really nothing wrong with classic
get the job done / less dev time
perfectly capable of
There's no point doing it "right" if you never finish.
concepts also apply to other , like
HTML5 canvas / SVG
3D Modeling
and if out & bought 3D printer
all the rage
then realized no idea CAD
no time to learn
no thousands of spare dollars
pro CAD software,
apply these old API concepts
modeling tool OpenSCAD
which uses all same brain cells
trained on OpenGL.
anyway
show code snippets
keep in mind
isn't how - write game
secretly dream of writing.
Unless was simple game and
graphics learning curve reason
never wrote it.
Still Deprecated, Though
:-(
Triangle Example
use OpenGL;
...; # Create window
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex2f(-1,-1);
glVertex2f( 1,-1);
glVertex2f( 0, 1);
glEnd();
...; # Show results, check for errors
Window Setup, Check Errors
use OpenGL;
use X11::GLX::DWIM;
my $glx= X11::GLX::DWIM->new;
$glx->target({window => { width => 400, height => 400 }});
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex2f(-1,-1);
glVertex2f( 1,-1);
glVertex2f( 0, 1);
glEnd();
$glx->swap_buffers;
if (my $x= glGetError()) { ... }
Animate
use OpenGL;
use X11::GLX::DWIM;
use Math::Trig 'deg2rad';
my $glx= X11::GLX::DWIM->new;
$glx->target({window => { width => 400, height => 400 }});
for my $angle (1..360) {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex2f(cos(deg2rad($angle )), sin(deg2rad($angle )));
glVertex2f(cos(deg2rad($angle + 120)), sin(deg2rad($angle + 120)));
glVertex2f(cos(deg2rad($angle + 240)), sin(deg2rad($angle + 240)));
glEnd();
$glx->swap_buffers;
if (my $x= glGetError()) { ... }
}
Animate
Animate the Triangle
Animate
use OpenGL;
use X11::GLX::DWIM;
use Math::Trig 'deg2rad';
my $glx= X11::GLX::DWIM->new;
$glx->target({window => { width => 400, height => 400 }});
for my $angle (1..360) {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex2f(cos(deg2rad($angle )), sin(deg2rad($angle )));
glVertex2f(cos(deg2rad($angle + 120)), sin(deg2rad($angle + 120)));
glVertex2f(cos(deg2rad($angle + 240)), sin(deg2rad($angle + 240)));
glEnd();
$glx->swap_buffers;
if (my $x= glGetError()) { ... }
}
Rotate A Static Model
use OpenGL;
use X11::GLX::DWIM;
my $glx= X11::GLX::DWIM->new();
for my $angle (1..360) {
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glRotated($angle, 0, 0, 1);
glBegin(GL_TRIANGLES);
glVertex2f( 1.000, 0.000 );
glVertex2f( -0.500, 0.866 );
glVertex2f( -0.500, -0.866 );
glEnd();
$glx->swap_buffers;
if (my $x= glGetError()) { ... }
}
Cache The Model
my $list_id= glGenLists(1); # allocate a list
glNewList($list_id, GL_COMPILE); # begin recording
glBegin(GL_TRIANGLES);
glVertex2f( 1.000, 0.000 );
glVertex2f( -0.500, 0.866 );
glVertex2f( -0.500, -0.866 );
glEnd();
glEndList();
for my $angle (1..360) {
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glRotated($angle, 0, 0, 1);
glCallList($list_id); # replay everything above
$glx->swap_buffers;
}
Not very friendly
Let's Perlify!
Type Less, Isolate Bugs
Display list is an allocated resource
glNewList/glEndList need matched
glBegin/glEnd need matched
Awkward names and constants
Display List Simplification
my $list_id= glGenLists(1); # allocate a list
glNewList($list_id, GL_COMPILE); # begin recording
glBegin(GL_TRIANGLES);
glVertex2f( 1.000, 0.000 );
glVertex2f( -0.500, 0.866 );
glVertex2f( -0.500, -0.866 );
glEnd();
glEndList();
sub compile_list(&) {
my $list= OpenGL::Sandbox::V1::DisplayList->new;
glNewList($list->id, GL_COMPILE);
eval $_[0];
glEndList();
die $@ if defined $@;
$list;
}
Begin/End Wrapper
sub triangles(&) {
glBegin(GL_TRIANGLES);
eval $_[0];
glEnd();
die $@ if defined $@;
}
Vertex Convenience
sub vertex {
@_ == 4? glVertex4d(@_)
: @_ == 3? glVertex3d(@_)
: @_ == 2? glVertex2d(@_)
: croak "Wrong number of arguments to vertex()"
}
Main Loop Cleanup
for my $angle (1..360) {
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glRotated($angle, 0, 0, 1);
$list->call; # replay everything above
$glx->swap_buffers;
}
Main Loop Cleanup
for my $angle (1..360) {
$glx->begin_frame;
glLoadIdentity();
glRotated($angle, 0, 0, 1);
$list->call; # replay everything above
$glx->end_frame;
}
Main Loop Cleanup
for my $angle (1..360) {
$glx->begin_frame;
local_matrix {
rotate z => $angle;
$list->call; # replay everything above
};
$glx->swap_buffers;
}
use Moo;
has filename => ( is => 'ro' );
has type => ( is => 'ro', required => 1, default => sub { 'FTTextureFont' } );
has data => ( is => 'ro', required => 1 );
has _ftgl_wrapper => ( is => 'lazy', handles => [qw(
face_size
ascender
descender
line_height
advance
)]);
sub _build__ftgl_wrapper {
my $self= shift;
my $class= __PACKAGE__.'::FTFontWrapper';
$class->new($self->data, $self->type);
}
Add Sugar to ->Render
FTGL render ok
want more layout
wrote in perl, but slow
construct/free hash options
void FTFontWrapper::render(const char *text, ...) {
... /* vars and things */
Inline_Stack_Vars;
if (Inline_Stack_Items & 1)
/* stack items includes $self and $text, and key=>value after that */
croak("Odd number of parameters passed to ->render");
for (i= 2; i < Inline_Stack_Items-1; i+= 2) {
key= SvPV_nolen(Inline_Stack_Item(i));
value= Inline_Stack_Item(i+1);
if (!SvOK(value)) continue; /* ignore anything that isn't defined */
switch (*key) {
case 'x': if (!key[1]) x= SvNV(value);
else if (strcmp("xalign", key) == 0) xalign= SvNV(value);
else
case 'y': if (!key[1]) y= SvNV(value);
else if (strcmp("yalign", key) == 0) {