Creating the Graphics of the DeLorean Digital Dashboard

Michael Conrad
mike@nrdvana.net
http://nrdvana.net/
CPAN: NERDVANA

Recap

New Modules

But more Practically...

Building Blocks

Legos

Lego is a registered trademark of The Lego Group

Erector

The Erector brand is owned by Spin Master Inc.

Science Olympiad

Image from www.hammacher.com

Software

Use the Right Tool

Middle Gound?

Things to Glue with Perl

XS

Inline Makes It Easy

Perl and C(++)

Disclaimer...

OpenGL, Classic (1.x)

OpenGL, Modern (2.x-4.x)

Needs?

Use the Right Tool

Modern:

Seascape, by TDM
(as seen on shadertoy.com)

Modern:

Glow Shader Test 2, by remonvv
(as seen on shadertoy.com)

Disadvantages:

There's no point doing it
"right"
if you never finish.

Classic works fine for:

glxgears

Classic works fine for:

Robot.sh

Classic works fine for:

FlightSim.sh

Classic works fine for:

DeLorean Dashboard

Classic works fine for:

Final Fantasy VII, © Square Enix

Other Relevance:

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;
  }
	

Type Less, Isolate Bugs

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();

	

Display List Simplification



  my $dlist= compile_list {
    glBegin(GL_TRIANGLES);
    glVertex2f(  1.000,  0.000 );
    glVertex2f( -0.500,  0.866 );
    glVertex2f( -0.500, -0.866 );
    glEnd();
  };
  $dlist->call;

	

glBegin/glEnd pairing



  my $dlist= compile_list {
    triangles {
      glVertex2f(  1.000,  0.000 );
      glVertex2f( -0.500,  0.866 );
      glVertex2f( -0.500, -0.866 );
    };
  };
  $dlist->call;

	

Vertex simplification



  my $dlist= compile_list {
    triangles {
      vertex  1.000,  0.000;
      vertex -0.500,  0.866;
      vertex -0.500, -0.866;
    };
  };
  $dlist->call;

	

Display List Wrapper



  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;
  }
	

Rotate Wrapper



  sub rotate {
    @_ == 4? glRotated(@_)
    : $_[0] eq 'x'? glRotated($_[1], 1, 0, 0)
    : $_[0] eq 'y'? glRotated($_[1], 0, 1, 0)
    : $_[0] eq 'z'? glRotated($_[1], 0, 0, 1)
    : croak "Invalid arguments to rotate";
  }

	

Matrix Wrapper



  sub local_matrix(&) {
    glPushMatrix();
    eval $_[0];
    glPopMatrix();
    die $@ if defined $@;
  }
  
	

All Together


  my $list= compile_list {
    triangles {
      vertex  1.000,  0.000;
      vertex -0.500,  0.866;
      vertex -0.500, -0.866;
    };
  };
  for my $angle (1..360) {
    $glx->begin_frame;
    local_matrix {
        rotate z => $angle;
        $list->call; # replay everything above
    };
    $glx->swap_buffers;
  }
	

Syntactic Sugar

Inline Microbenchmak



   #! /usr/bin/env perl
   use Inline C => "
     int foo_c(int x) { return 10 * x; }
     void bar_c() { return; }
   ";
   sub foo_perl {
      my $x= shift;
      return $x * 10;
   }
   sub foo_minimal { $_[0] * 10 }
   sub bar_perl {}
	

Inline Microbenchmark


   use Benchmark qw( :hireswallclock cmpthese );
   cmpthese(40_000_000, {
      foo_c       => sub { foo_c(42) },
      foo_perl    => sub { foo_perl(42) },
      foo_minimal => sub { foo_minimal(42) },
      bar_c       => sub { bar_c(); },
      bar_perl    => sub { bar(); },
   });
	

Benchmark Results


                  Rate   foo_perl foo_minimal   bar_perl       foo_c       bar_c
foo_perl    10362694/s         --        -27%       -49%        -71%        -77%
foo_minimal 14184397/s        37%          --       -30%        -60%        -69%
bar_perl    20202020/s        95%         42%         --        -43%        -56%
foo_c       35398230/s       242%        150%        75%          --        -22%
bar_c       45454545/s       339%        220%       125%         28%          --

	
C always wins

Inline vertex


  sub vertex {
    @_ == 4?   glVertex4d(@_)
    : @_ == 3? glVertex3d(@_)
    : @_ == 2? glVertex2d(@_)
    : croak "Wrong number of arguments to vertex()"
  }
	

Inline vertex


  void vertex(double x, double y, ...) {
    Inline_Stack_Vars;
    switch (Inline_Stack_Items) {
    case 4: glVertex4d( x, y, SvNV(Inline_Stack_Item(2)), SvNV(Inline_Stack_Item(3)) );
            break;
    case 3: glVertex3d( x, y, SvNV(Inline_Stack_Item(2)) );
            break;
    case 2: glVertex3d( x, y, );
            break;
    default: croak("Too many arguments for vertex(): %d", Inline_Stack_Items);
    }
    Inline_Stack_Void;
  }
	

Inline rotate


  sub rotate {
    @_ == 4? glRotated(@_)
    : $_[0] eq 'x'? glRotated($_[1], 1, 0, 0)
    : $_[0] eq 'y'? glRotated($_[1], 0, 1, 0)
    : $_[0] eq 'z'? glRotated($_[1], 0, 0, 1)
    : croak "Invalid arguments to rotate()";
  }
	

Inline rotate


  void rotate(SV *arg0, double arg1, ...) {
    Inline_Stack_Vars;
    const char *axis;
    if (Inline_Stack_Items == 4) {
       glRotated(SvNV(arg0), arg1, SvNV(Inline_Stack_Item(2)), SvNV(Inline_Stack_Item(3)));
    }
    else if (Inline_Stack_Items == 2 && SvPOK(flag)) {
      axis= SvPVX(arg0);
      switch (axis[0]) {
      case 'x': if (axis[1] == '\0') glRotated(arg1, 1, 0, 0); else
      case 'y': if (axis[1] == '\0') glRotated(arg1, 0, 1, 0); else
      case 'z': if (axis[1] == '\0') glRotated(arg1, 0, 0, 1); else
      default: croak("Expected 'x', 'y', or 'z' as first argument rotate(axis => $angle)");
      }
    }
    else croak("rotate requires 2 or 4 arguments");
    Inline_Stack_Void;
  }
	

Inline triangles



  sub triangles(&) {
    glBegin(GL_TRIANGLES);
    eval $_[0];
    glEnd();
    die $@ if defined $@;
  }

	

Inline triangles


  void triangles(SV* code) {
    glBegin(GL_TRIANGLES);
    call_sv(code, G_ARRAY|G_EVAL);
    glEnd();
    if (SvTRUE(ERRSV)) croak(NULL);
  }
	

Inline triangles


    if (SvTRUE(DOLLARSIGN_AT)) croak(NULL);
	

Inline::CPP, and Fonts

http://ftgl.sourceforge.net/docs/html/

Wrapping FTGL


class FTFontWrapper {
   SV *mmap_obj;
   FTFont *font;
public:
   FTFontWrapper(SV *mmap, const char *font_class);
   ~FTFontWrapper();
   ...
	

Wrapping FTGL


...
   double ascender()     { return font->Ascender(); }
   double descender()    { return font->Descender(); }
   double line_height()  { return font->LineHeight(); }

   int face_size(...);
   
   void depth(float d);
   void outset(...);
   void use_display_list(bool enable);

   double advance(const char *text);
   void render(const char *text, ...);
};
	

Wrapping the Wrapper


   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


   $font->render(
      "Hello World!",
      x => 2, y => 2,
      x_align => 0.5,
      y_align => 1,
      monospace => 1,
      scale => 1/72,
      width => 4.7
   );
	

Move Sugar to C++


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) {
	

Sugar in Action


# Render with baseline at origin
show {
	draw_boundbox( -50, $font->descender, 50, $font->ascender );
	$font->render("Right Baseline", xalign => 1);
};
show {
	draw_boundbox( -50, $font->descender, 50, $font->ascender );
	$font->render('Center Baseline', xalign => .5);
};
show {
	draw_boundbox( -50, $font->descender, 50, $font->ascender );
	$font->render("Top", xalign => .5, yalign => 1);
};
show {
	draw_boundbox( -50, $font->descender, 50, $font->ascender );
	$font->render("Center", xalign => .5, yalign => .5);
};
	

Sugar in Action

prove -lv t/90-visual-inspection.t

Speedometer Ticks

Speedometer Ticks

localmatrix {
   my $max= $self->gauge_max;
   my $inc= $self->gauge_tick_interval;
   my $count= 0;
   for (my $s= 0; $s <= $max+0.1; $s+= $inc) {
      ++$count;
      my $angle= $self->gauge_angle_fn->($s);
      my ($sin, $cos)= sincos($angle);
      if ($count & 1) {
         $font->render(sprintf("%2d", $s+0.001),
            x => $cos * $self->gauge_text_rad, y => $sin * $self->gauge_text_rad,
            height => $self->gauge_text_size,
            v_align=> 'center', h_align => 'center'
         );
      }
      localmatrix {
         trans $cos * $self->gauge_tick_rad, $sin * $self->gauge_tick_rad;
         rotate z => $angle - 90;
         $rect_16_32->render(center => 1,
           w => ($count&1)? 0.12 : 0.07,
           h => ($count&1)? 0.6 : 0.4);
      };
   }
};

Side Effects of Inline

Inline vs. XS

Packaging Inline for CPAN

Packaging Inline for CPAN

package inc::InlineMakeMaker;
use Moose;

extends 'Dist::Zilla::Plugin::MakeMaker::Awesome';

override _build_MakeFile_PL_template => sub {
	my $tpl= shift->next::method(@_);
	$tpl =~ s/ExtUtils::MakeMaker/Inline::MakeMaker/g;
	return $tpl;
};
override register_prereqs => sub {
	$_[0]->zilla->register_prereqs(
		{ phase => 'configure' },
		'Inline::MakeMaker' => 0.45,
	);
	shift->next::method(@_);
};

__PACKAGE__->meta->make_immutable;

dist.ini

...
[Git::NextVersion]
first_version = 0.01
[PkgVersion]
use_begin = 0
[CheckLib]
lib = GL
lib = swscale
header = GL/gl.h
header = libswscale/swscale.h
header = libavutil/pixfmt.h
header = libavutil/avutil.h
debug = 1
[=inc::InlineMakeMaker / InlineMakeMaker]
[Manifest]
[License]
[Readme]
...

Packaging Inline for CPAN


use Inline
	C => do { my $x= __FILE__; $x =~ s|\.pm|\.c|; Cwd::abs_path($x) },
	(defined $OpenGL::Sandbox::Texture::VERSION? (
		NAME => __PACKAGE__,
		VERSION => __PACKAGE__->VERSION
	) : () ),
	INC => '-I'.do{ my $x= __FILE__; $x =~ s|/[^/]+$|/|; Cwd::abs_path($x) }
	      .' -I/usr/include/ffmpeg',
	LIBS => '-lGL -lswscale',
	CCFLAGSEX => '-Wall -g3 -Os';
	

My Modules

Related Modules