C vs Perl | Perl vs Python | POGL vs SDL | Windows vs Linux |

This is a capture of a POGL benchmark (bench.pl),
using FBO for animating textures, using a blacklight shader. POGL Benchmarks - C vs Perl

Google Bookmarks Google
  
Digg It! digg it!
  
Stumble Upon It! Stumbled on It!
  
del.icio.us del.icio.us
  
reddit! reddit
  
Slashdot It! Slashdot It!

This is a benchmark implemented in C and Perl, using animated FBOs and a fragment shader.

Platforms

These benchmarks were performed on the same multi-boot system:

  • 3Ghz Intel Pentium D
  • 1G Memory
  • nVidia GeForce 6800 GT/AGP/SSE2
  • Matched nVidia drivers (as of March, 15, 2007):
    • Linux: v97.55
    • Vista: v100.65

The tests were run using portable, non-OOP C and Perl code on:

  • Vista
  • Fedora 6
  • Ubuntu/Dapper

The C benchmarks require an OpenGL installation and GLUT. The Perl benchmarks also require Perl, POGL and the Time::HiRes module.

These tests were also run on a similar XP system, with similar results; however, since it was not possible to install XP after Vista and Linux were already installed on this benchmark system, those results were not included in this report.

Summary

  • No statistical difference in overall performance between C and Perl
  • Perl outperformed C on FBO operations; C beat Perl on Shader ops
  • On Vista, Perl occasionally/often ran faster than C
  • Fedora and Ubuntu were comparable in performance
  • Linux was generally 60% faster than Vista

Results

Vista Fedora 6 Ubuntu/Dapper
PROFILE CPerl CPerl CPerl
FBO Texture Rendering FPS 117.296043137.680194 190.628178192.675391 193.914733194.749544
Teapot Shader FPS 267.217852284.675682 479.408799472.232557 472.171695477.404038
Frame overhead secs/frame 0.0000320.000126 0.0000300.000071 0.0000330.000067
OS/GLUT overhead secs/frame 0.0000000.000050 0.0000060.000014 0.0001140.000114
TOTAL FPS 81.30279291.315494 135.728814135.275647 134.742228134.945798

Analysis

There are a number of factors explaining the lack of significant difference between C and Perl OpenGL performance:

  • POGL is a compiled C module
  • Most of the work is performed by the GPU
  • POGL leverages OpenGL::Array (OGA) objects

OGA stores data in typed C arrays, eliminating the need for conversion/copy/casting when passing array data to OpenGL APIs.

Perl OpenGL provides a number of other advantages:

  • Completely portable, with no compiling; faster prototyping
  • Easy integration into LAMP-based online services
  • Improved string handling for dynamic shader programming

Source Code

C Binding


// ogl_bench v1.0 - Copyright 2007 - Graphcomp
// Bob Free bfree@graphcomp.com
// http://graphcomp.com/opengl

// This program is freely distributable without licensing fees 
// and is provided without guarantee or warrantee expressed or 
// implied. This program is -not- in the public domain.


// Set up standard libs
#include <stdlib.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <stdio.h>
#include <string.h>


// Set up constants
#define PROGRAM "OpenGL Benchmark - C Binding"
#define CYCLES 1000


// Setup OpenGL Extensions
#include <GL/glut.h>

#ifdef _WIN32
#define GL_GLEXT_PROCS
#define loadProc(proc,name) \
if (!proc) \
{ \
  proc = (void *)wglGetProcAddress(name); \
  if (!proc) error("Unable to load",name); \
}
#define timeb _timeb
#define ftime _ftime
#else
#define GL_GLEXT_PROTOTYPES
#define loadProc(proc,name)
#endif
#include "glext_procs.h"

// Set up types
typedef struct
{
  struct timeb start;
  float secs;
} Bench;


// Set up globals
Bench appBench;
Bench frameBench;
Bench textureBench;
Bench teapotBench;
struct timeb now;
int frames = 0;

GLuint idWindow = 0;

GLint windowWidth = 512;
GLint windowHeight = 512;

GLint textureWidth = 128;
GLint textureHeight = 128;

GLfloat rotTeapotX = 0;
GLfloat rotTeapotY = 0;

GLuint idTexture = 0;
GLuint idFrameBuffer = 0;
GLuint idRenderBuffer = 0;
GLuint idVertexProg = 0;
GLuint idFragProg = 0;

GLdouble incY = 0.5;
GLdouble rotY = 0.0;


// Start benchmark
void startBench(Bench *pBench)
{
  ftime(&pBench->start);
}

// Accumulate benchmark
void endBench(Bench *pBench)
{
  ftime(&now);
  pBench->secs += (float)(now.time - pBench->start.time) +
    (float)((now.millitm - pBench->start.millitm) / 1000.0);
}

// Print benchmark
void printBench()
{
  float overhead;

  if (!frames || !appBench.secs || !frameBench.secs ||
    !textureBench.secs || !teapotBench.secs)
  {
    printf("No measurable time has elapsed\n");
    return;
  }

  printf("FBO Texture Rendering FPS: %f\n", frames / textureBench.secs);
  printf("Teapot Shader FPS: %f\n", frames / teapotBench.secs);

  overhead = frameBench.secs - (textureBench.secs + teapotBench.secs);
  printf("Frame overhead secs/frame: %f\n", overhead / frames);

  overhead = appBench.secs - frameBench.secs;
  printf("OS/GLUT overhead secs/frame: %f\n", overhead / frames);

  printf("Overall FPS: %f\n", frames / appBench.secs);
  printf("\n");
}

// Error handling
void error(char *errTitle, char *errMsg)
{
  printf("%s: %s\n", errTitle, errMsg);
  exit(0);
}

// Check OpenGL Version
void checkVersion(void)
{
  char *version = (char*)glGetString(GL_VERSION);
  char *vendor = (char*)glGetString(GL_VENDOR);
  char *renderer = (char*)glGetString(GL_RENDERER);
  char *exts = (char*)glGetString(GL_EXTENSIONS);

  printf("%s\n\n", PROGRAM);
  printf("OpenGL: %s\n", version);
  printf("Vendor: %s\n", vendor);
  printf("Renderer: %s\n", renderer);
  printf("\n");

  if (!strstr(exts,"EXT_framebuffer_object"))
  {
    error("Extension not available","EXT_framebuffer_object");
  }
}

// Load Extension Procs
void initExtensions(void)
{
  loadProc(glIsRenderbufferEXT,"glIsRenderbufferEXT");
  loadProc(glBindRenderbufferEXT,"glBindRenderbufferEXT");
  loadProc(glDeleteRenderbuffersEXT,"glDeleteRenderbuffersEXT");
  loadProc(glGenRenderbuffersEXT,"glGenRenderbuffersEXT");
  loadProc(glRenderbufferStorageEXT,"glRenderbufferStorageEXT");
  loadProc(glGetRenderbufferParameterivEXT,"glGetRenderbufferParameterivEXT");
  loadProc(glIsFramebufferEXT,"glIsFramebufferEXT");
  loadProc(glBindFramebufferEXT,"glBindFramebufferEXT");
  loadProc(glDeleteFramebuffersEXT,"glDeleteFramebuffersEXT");
  loadProc(glGenFramebuffersEXT,"glGenFramebuffersEXT");
  loadProc(glCheckFramebufferStatusEXT,"glCheckFramebufferStatusEXT");
  loadProc(glFramebufferTexture1DEXT,"glFramebufferTexture1DEXT");
  loadProc(glFramebufferTexture2DEXT,"glFramebufferTexture2DEXT");
  loadProc(glFramebufferTexture3DEXT,"glFramebufferTexture3DEXT");
  loadProc(glFramebufferRenderbufferEXT,"glFramebufferRenderbufferEXT");
  loadProc(glGetFramebufferAttachmentParameterivEXT,"glGetFramebufferAttachmentParameterivEXT");
  loadProc(glGenerateMipmapEXT,"glGenerateMipmapEXT");

  loadProc(glGenProgramsARB,"glGenProgramsARB");
  loadProc(glBindProgramARB,"glBindProgramARB");
  loadProc(glProgramStringARB,"glProgramStringARB");
  loadProc(glDeleteProgramsARB,"glDeleteProgramsARB");
}

// Initialize Vertex/Fragment Programs
void initProgs(void)
{
  // NOP Vertex shader
  const char *vertexProg =
    "!!ARBvp1.0\n"
    "TEMP vertexClip;\n"
    "DP4 vertexClip.x, state.matrix.mvp.row[0], vertex.position;\n"
    "DP4 vertexClip.y, state.matrix.mvp.row[1], vertex.position;\n"
    "DP4 vertexClip.z, state.matrix.mvp.row[2], vertex.position;\n"
    "DP4 vertexClip.w, state.matrix.mvp.row[3], vertex.position;\n"
    "MOV result.position, vertexClip;\n"
    "MOV result.color, vertex.color;\n"
    "MOV result.texcoord[0], vertex.texcoord;\n"
    "MOV result.texcoord[1], vertex.normal;\n"
    "END\n";


  // Black Light Fragment shader
  const char *fragProg =
    "!!ARBfp1.0\n"
    "TEMP decal,color;\n"
    "TEX decal, fragment.texcoord[0], texture[0], 2D;\n"
    "MUL result.color, decal, fragment.texcoord[1];\n"
    "END\n";


  glGenProgramsARB(1,&idVertexProg);
  glGenProgramsARB(1,&idFragProg);

  glBindProgramARB(GL_VERTEX_PROGRAM_ARB, idVertexProg);
  glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
    (GLsizei)strlen(vertexProg), vertexProg);

  glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, idFragProg);
  glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
    (GLsizei)strlen(fragProg), fragProg);
}

// Terminate Vertex/Fragment Programs
void termProgs(void)
{
  glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
  glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);

  glDeleteProgramsARB(1,&idVertexProg);
  glDeleteProgramsARB(1,&idFragProg);
}

// FBO Status handler
void statusFBO(void)
{
  GLenum stat = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  if (!stat || stat == GL_FRAMEBUFFER_COMPLETE_EXT) {return;};
  printf("FBO status: %04X\n", stat);
  exit(0);
}

// Initialize Framebuffers
void initFBO(void)
{
  glGenTextures(1,&idTexture);
  glGenFramebuffersEXT(1,&idFrameBuffer);
  glGenRenderbuffersEXT(1,&idRenderBuffer);

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, idFrameBuffer);
  glBindTexture(GL_TEXTURE_2D, idTexture);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, textureWidth, textureHeight,
		0, GL_RGBA, GL_UNSIGNED_BYTE, 0);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
    GL_TEXTURE_2D, idTexture, 0);

  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, idRenderBuffer);

  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24,
		textureWidth, textureHeight);

  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
      GL_RENDERBUFFER_EXT, idRenderBuffer);

  statusFBO();
}

// FBO texture renderer
void renderFBO(void)
{
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, idFrameBuffer);

  glLoadIdentity();
  glTranslated(-0.75, -0.85, -2.5);

  glRotated(rotTeapotX, 1.0, 0.0, 0.0);
  rotTeapotX += .5;

  glRotated(rotTeapotY, 0.0, 1.0, 0.0);
  rotTeapotY += 1.0;

  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glColor3d(1.0, 1.0, 1.0);
 
  glutWireTeapot(0.125);

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

// Terminate FBO objects
void termFBO(void)
{
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
  glBindTexture(GL_TEXTURE_2D, 0);

  glDeleteRenderbuffersEXT(1,&idRenderBuffer);
  glDeleteFramebuffersEXT(1,&idFrameBuffer);
  glDeleteTextures(1,&idTexture);
}

// Resize Window
void resizeScene(GLint width, GLint height)
{
  if (!height){height = 1;};

  glViewport(0, 0, width, height);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0,width/height,0.1,100.0);

  glMatrixMode(GL_MODELVIEW);

  windowWidth  = width;
  windowHeight = height;
}

// Initialize OpenGL Environment
void init(void)
{
  checkVersion();
  initExtensions();

  resizeScene(windowWidth, windowHeight);

  initFBO();
  initProgs();
}

// Terminate OpenGL Environment
void term(void)
{
  // Display benchmark
  endBench(&appBench);
  printBench();

  // Disable app
  glutHideWindow();
  glutKeyboardFunc(0);
  glutSpecialFunc(0);
  glutIdleFunc(0);
  glutReshapeFunc(0);

  // Release Framebuffers
  termProgs();
  termFBO();

  // Now we can destroy window
  glutDestroyWindow(idWindow);
  exit(0);
}

// Frame handler
void display(void)
{
  // Run benchmark CYCLES times
  if (++frames > CYCLES) {term();};
  startBench(&frameBench);

  // Render animated texture
  startBench(&textureBench);
  renderFBO();
  endBench(&textureBench);
  
  // Set up ModelView
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
  glRotated(0.0,1.0,0.0,0.0);
  glRotated(rotY,0.0,1.0,0.0);
  rotY += incY;

  // Set attributes
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);

  // Clear render buffer and set teapot color
  glClearColor((float)0.2, (float)0.2, (float)0.2, (float)1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glColor3d(0.9, 0.45, 0.0);

  // Render the teapot using our shader
  startBench(&teapotBench);
  glEnable(GL_VERTEX_PROGRAM_ARB);
  glEnable(GL_FRAGMENT_PROGRAM_ARB);
 
  glutSolidTeapot(1.0);

  glDisable(GL_FRAGMENT_PROGRAM_ARB);
  glDisable(GL_VERTEX_PROGRAM_ARB);
  endBench(&teapotBench);

  // Double-buffer and done
  glutSwapBuffers();
  endBench(&frameBench);
}

// Keyboard handler
void keyPressed(GLubyte key, GLint x, GLint y)
{
  term();
}



// Main app
int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_ALPHA);
  glutInitWindowSize(windowWidth,windowHeight);

  idWindow = glutCreateWindow(PROGRAM);

  glutDisplayFunc(display);
  glutIdleFunc(display);
  //glutReshapeFunc(resizeScene);
  glutKeyboardFunc(keyPressed);

  init();
  startBench(&appBench);
  glutMainLoop();
  return(0);
}
Perl Binding
#!/usr/bin/perl -w

# ogl_bench v1.0 - Copyright 2007 - Graphcomp
# Bob Free bfree@graphcomp.com
# http://graphcomp.com/opengl

# This program is freely distributable without licensing fees 
# and is provided without guarantee or warrantee expressed or 
# implied. This program is -not- in the public domain.


# Set up standard libs
use strict;
use Math::Trig;
eval 'use Time::HiRes qw( gettimeofday )';
my $hasHires = !$@; # Only appBench will be valid if no HiRes
sub ftime{return $hasHires ? gettimeofday() : time()}


# Set up constants
use constant PROGRAM => "OpenGL Benchmark - Perl Binding";
use constant CYCLES => 1000;


# Setup OpenGL Extensions
use OpenGL qw/ :all /;

























# Set up globals
my $appBench = {};
my $frameBench = {};
my $textureBench = {};
my $teapotBench={};
my $now;
my $frames = 0;

my $idWindow = 0;

my $windowWidth = 512;
my $windowHeight = 512;

my $textureWidth = 128;
my $textureHeight = 128;

my $rotTeapotX = 0;
my $rotTeapotY = 0;

my $idTexture = 0;
my $idFrameBuffer = 0;
my $idRenderBuffer = 0;
my $idVertexProg = 0;
my $idFragProg = 0;

my $incY = 0.5;
my $rotY = 0.0;


# Start benchmark
sub startBench
{ my($pBench) = @_;
  $pBench->{start} = ftime();
}

# Accumulate benchmark
sub endBench
{ my($pBench) = @_;
  $now = ftime();
  $pBench->{secs} += $now - $pBench->{start};
}


# Print benchmark
sub printBench
{
  my $overhead;

  if (!$frames || !$appBench->{secs} || !$frameBench->{secs} ||
    !$textureBench->{secs} || !$teapotBench->{secs})
  {
    printf("No measurable time has elapsed\n");
    return;
  }

  printf("FBO Texture Rendering FPS: %f\n", $frames / $textureBench->{secs});
  printf("Teapot Shader FPS: %f\n", $frames / $teapotBench->{secs});

  $overhead = $frameBench->{secs} - ($textureBench->{secs} + $teapotBench->{secs});
  printf("Frame overhead secs/frame: %f\n", $overhead / $frames);

  $overhead = $appBench->{secs} - $frameBench->{secs};
  printf("OS/GLUT overhead secs/frame: %f\n", $overhead / $frames);

  printf("Overall FPS: %f\n", $frames / $appBench->{secs});
  printf("\n");
}

# Error handling
sub error
{ my($errTitle,$errMsg) = @_;
  printf("%s: %s\n", $errTitle, $errMsg);
  exit(0);
}

# Check OpenGL Version
sub checkVersion
{
  my $version = glGetString(GL_VERSION);
  my $vendor = glGetString(GL_VENDOR);
  my $renderer = glGetString(GL_RENDERER);
  my $exts = glGetString(GL_EXTENSIONS);

  printf("%s\n\n", PROGRAM);
  printf("OpenGL: %s\n", $version);
  printf("Vendor: %s\n", $vendor);
  printf("Renderer: %s\n", $renderer);
  printf("\n");

  if ($exts !~ m|EXT_framebuffer_object|)
  {
    error("Extension not available","EXT_framebuffer_object");
  }
}




























# Initialize Vertex/Fragment Programs
sub initProgs
{
  # NOP Vertex shader
  my $vertexProg = qq
  {!!ARBvp1.0
    TEMP vertexClip;
    DP4 vertexClip.x, state.matrix.mvp.row[0], vertex.position;
    DP4 vertexClip.y, state.matrix.mvp.row[1], vertex.position;
    DP4 vertexClip.z, state.matrix.mvp.row[2], vertex.position;
    DP4 vertexClip.w, state.matrix.mvp.row[3], vertex.position;
    MOV result.position, vertexClip;
    MOV result.color, vertex.color;
    MOV result.texcoord[0], vertex.texcoord;
    MOV result.texcoord[1], vertex.normal;
    END
  };

  # Black Light Fragment shader
  my $fragProg = qq
  {!!ARBfp1.0
    TEMP decal,color;
    TEX decal, fragment.texcoord[0], texture[0], 2D;
    MUL result.color, decal, fragment.texcoord[1];
    END
  };

  ($idVertexProg,$idFragProg) = glGenProgramsARB_p(2);


  glBindProgramARB(GL_VERTEX_PROGRAM_ARB, $idVertexProg);
  glProgramStringARB_p(GL_VERTEX_PROGRAM_ARB, $vertexProg);


  glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, $idFragProg);
  glProgramStringARB_p(GL_FRAGMENT_PROGRAM_ARB, $fragProg);

}

# Terminate Vertex/Fragment Programs
sub termProgs
{
  glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
  glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);

  glDeleteProgramsARB_p($idVertexProg,$idFragProg);

}

# FBO Status handler
sub statusFBO
{
  my $stat = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  if (!$stat || $stat == GL_FRAMEBUFFER_COMPLETE_EXT) {return;};
  printf("FBO status: %04X\n", $stat);
  exit(0);
}

# Initialize Framebuffers
sub initFBO
{
  ($idTexture) = glGenTextures_p(1);
  ($idFrameBuffer) = glGenFramebuffersEXT_p(1);
  ($idRenderBuffer) = glGenRenderbuffersEXT_p(1);

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, $idFrameBuffer);
  glBindTexture(GL_TEXTURE_2D, $idTexture);

  glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $textureWidth, $textureHeight,
		0, GL_RGBA, GL_UNSIGNED_BYTE, 0);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
    GL_TEXTURE_2D, $idTexture, 0);

  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, $idRenderBuffer);

  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24,
		$textureWidth, $textureHeight);

  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
      GL_RENDERBUFFER_EXT, $idRenderBuffer);

  statusFBO();
}

# FBO texture renderer
sub renderFBO
{
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, $idFrameBuffer);

  glLoadIdentity();
  glTranslated(-0.75, -0.85, -2.5);

  glRotated($rotTeapotX, 1.0, 0.0, 0.0);
  $rotTeapotX += .5;

  glRotated($rotTeapotY, 0.0, 1.0, 0.0);
  $rotTeapotY += 1.0;

  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glColor3d(1.0, 1.0, 1.0);
 
  glutWireTeapot(0.125);

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

# Terminate FBO objects
sub termFBO
{
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
  glBindTexture(GL_TEXTURE_2D, 0);

  glDeleteRenderbuffersEXT_p($idRenderBuffer);
  glDeleteFramebuffersEXT_p($idFrameBuffer);
  glDeleteTextures_p($idTexture);
}

# Resize Window
sub resizeScene
{ my($width,$height) = @_;
  if (!$height){$height = 1;};

  glViewport(0, 0, $width, $height);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0,$width/$height,0.1,100.0);

  glMatrixMode(GL_MODELVIEW);

  $windowWidth  = $width;
  $windowHeight = $height;
}

# Initialize OpenGL Environment
sub init
{
  checkVersion();


  resizeScene($windowWidth, $windowHeight);

  initFBO();
  initProgs();
}

# Terminate OpenGL Environment
sub term
{
  # Display benchmark
  endBench($appBench);
  printBench();

  # Disable app
  glutHideWindow();
  glutKeyboardFunc(0);
  glutSpecialFunc(0);
  glutIdleFunc(0);
  glutReshapeFunc(0);

  # Release Framebuffers
  termProgs();
  termFBO();

  # Now we can destroy window
  glutDestroyWindow($idWindow);
  exit(0);
}

# Frame handler
sub display
{
  # Run benchmark CYCLES times
  if (++$frames > CYCLES) {term();};
  startBench($frameBench);

  # Render animated texture
  startBench($textureBench);
  renderFBO();
  endBench($textureBench);
  
  # Set up ModelView
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
  glRotated(0.0,1.0,0.0,0.0);
  glRotated($rotY,0.0,1.0,0.0);
  $rotY += $incY;

  # Set attributes
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);

  # Clear render buffer and set teapot color
  glClearColor(0.2, 0.2, 0.2, 1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glColor3d(0.9, 0.45, 0.0);

  # Render the teapot using our shader
  startBench($teapotBench);
  glEnable(GL_VERTEX_PROGRAM_ARB);
  glEnable(GL_FRAGMENT_PROGRAM_ARB);
 
  glutSolidTeapot(1.0);

  glDisable(GL_FRAGMENT_PROGRAM_ARB);
  glDisable(GL_VERTEX_PROGRAM_ARB);
  endBench($teapotBench);

  # Double-buffer and done
  glutSwapBuffers();
  endBench($frameBench);
}

# Keyboard handler
sub keyPressed
{
  term();
}



# Main app


glutInit(@ARGV);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_ALPHA);
glutInitWindowSize($windowWidth,$windowHeight);

$idWindow = glutCreateWindow(PROGRAM);

glutDisplayFunc(\&display);
glutIdleFunc(\&display);
#glutReshapeFunc(\&resizeScene);
glutKeyboardFunc(\&keyPressed);

init();
startBench($appBench);
glutMainLoop();
exit(0);

Note: Perl OpenGL (POGL) has no OS-dependencies; as such, you'll notice a bit of whitespace in the Perl code compared to C. POGL also provides additional Perl-ish OpenGL APIs (suffixed with "_p") to simplify string and array handling.

While clearly there are differences between C and Perl, the similarities make it relatively easy to port code between the two.

If you would like to add Java/Python/Ruby benchmarks to this comparison, please send the POGL team your port.

Note: As of this writing, Java OpenGL (JOGL) does not support FBOs, but rather platform-specific pBuffers, which are known to have poorer performance than FBOs.

Downloads

This benchmark source and Win32/Linux binaries are available on this site

Review the README for build instructions.