GlxGears

"Shawn P. Garbett" <[email protected]>
Newsgroups gmane.comp.lang.haskell.hopengl
Message-ID <[email protected]>
I translated GlxGears to HOpenGl, and it runs a bit slower than C 
version. I was curious what optimizations could be made to run it 
faster, or other comments. I love the style of Haskell, it leads one 
down the road to strong cohesion and low coupling as the easiest path. 
Even if all out speed was a top concern, I think it would be 
faster/more effective to write it in Haskell, then convert it to C.

Haskell Version on my laptop, FPS = 103
C Version on my laptop, FPS = 192

I've included the source as text. Enjoy-- all constructive criticism 
appreciated. Beware the line wrap...

Shawn
  
-----------------------------------------------------------------------------
 --
-- 
-- Copyright (c) 2004 Shawn P. Garbett
-- All rights reserved.
--
-- Redistribution and use in sourse and binary forms are permitted
-- provided that the above copyright notice and this paragraph are
-- duplicated in all such forms and that any documentation,
-- advertising materials, and other materials related to such
-- distribution and use acknowledge that the software was developed
-- by Shawn P. Garbett. The name of Shawn P. Garbett 
-- may not be used to endorse or promote products derived from this
-- software without specific prior writte permission.
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EPXRESS OR
-- IMPLIED WARRANTIES, INCLUDING LIMITATION, THE IMPLIED
-- WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
--
-------------------------------------------------------------------------------

  
-----------------------------------------------------------------------------
 --
-- Author -
--     Shawn P. Garbett
--     eLucid Software
--     March, 2004
-- Modifications -
--
-- Status -
--     Public Domain. Distribution Unlimited.
--
-- Bugs -
--     The -i option prints nothing
--     Auto exit not complete
--     Why is it so slow compared to C? 103 fps in Haskell versus 192 
fps in C
--
-- Compile: ghc -package GLUT -o Gears Gears.hs
--
-------------------------------------------------------------------------------
 

import Graphics.UI.GLUT  as GLUT
import Graphics.Rendering.OpenGL as OpenGL

import System.Exit
import Data.IORef

import GHC.Base (chr)

import System.Console.GetOpt
import System.Environment (getArgs)

instance HasSetter IORef  where
  ($=) var val = writeIORef var val
 
instance HasGetter IORef  where
  get var = readIORef var

new = newIORef

type Frames   = IORef (Int, Int)
type View     = IORef (GLfloat, GLfloat, GLfloat)
type ViewFunc = 
((GLfloat->GLfloat),(GLfloat->GLfloat),(GLfloat->GLfloat))

pi :: GLfloat
pi  = 3.14159265

configure :: IO (DisplayList,DisplayList,DisplayList)
configure  = do
               position (Light 0) $= Vertex4 5.0 5.0 10.0 0.0
               lighting           $= Enabled
               light (Light 0)    $= Enabled
               depthFunc          $= Just Less
               g1 <- gear1
               g2 <- gear2
               g3 <- gear3
               normalize          $= Enabled
               return (g1, g2, g3)

-- Command line options (that start with a dash)
data Flag = GLInfo | Exit deriving Show

options :: [OptDescr Flag]
options  =
  [ Option ['i'] ["info"] (NoArg GLInfo) "print gl information",
    Option ['e'] ["exit"] (NoArg Exit)   "auto exit after 30 seconds" ]

usageHeader :: String
usageHeader  = "Usage: Gears [-info] [-exit]"

opts     :: [String] -> IO [Flag]
opts argv = 
  case (getOpt Permute options argv) of
    (o,_,[])   -> return o
    (_,_,errs) -> ioError $ userError $
                              concat errs ++ usageInfo usageHeader 
options


-- Print info about the GL renderer
info' :: IO ()
info'  =
  do
    rendererStr <- get renderer
    putStr "GL_RENDERER = "
    putStr rendererStr
    putStr "\n"
    vendorStr <- get vendor
    putStr "GL_VENDOR = "
    putStr vendorStr
    putStr "\n"
    versionStr <- get glVersion
    putStr "GL_VERSION = "
    putStr versionStr
    putStr "\n"
    extStr <- get glExtensions
    putStr "GL_EXTENSIONS = "
    putStr $ show extStr
    putStr "\n"

-- Was the info flag given?
info             :: [Flag] -> IO ()
info (GLInfo:_)  = info'
info (_:fs)      = info fs
info _           = return ()

-- Main 
main :: IO ()
main  =
  do
    (progName,args) <- getArgsAndInitialize
    flags           <- opts args
    info flags
    initialDisplayMode $= [RGBMode, WithDepthBuffer, DoubleBuffered]
    -- View rotation variable (x,y,z)
    viewRot <- new (20.0::GLfloat, 30.0::GLfloat, 0.0::GLfloat) 
    -- Gear angle variable
    angle   <- new (0.0::GLfloat)
    -- Frames
    frames  <- new (0, 0)
    -- Create the window
    createWindow progName
    gears   <- configure
    -- Hook up callbacks
    displayCallback       $= display gears frames viewRot angle
    reshapeCallback       $= Just reshape
    keyboardMouseCallback $= Just (keyboard viewRot)
    visibilityCallback    $= Just (visible angle)
    --go for it
    mainLoop

-- Reshape event handling
reshape :: Size -> IO ()
reshape s@(Size w h) = 
  do
    let r = (fromIntegral h)/(fromIntegral w)
    viewport     $= (Position 0 0, s)
    matrixMode   $= Projection
    loadIdentity
    frustum      (-1.0) 1.0 (-r) r 5.0 60.0
    matrixMode   $= Modelview 0
    loadIdentity
    translate    (Vector3 0 0 (-40.0::GLfloat))

-- Visibility event handling
visible                 :: IORef GLfloat -> Visibility -> IO ()
visible angle Visible    = idleCallback $= Just (idle angle)
visible _     NotVisible = idleCallback $= Nothing

-- Idle event handling
idle      :: IORef GLfloat -> IO ()
idle angle = do
               a <- get angle
               angle $= a + 2.0;
               postRedisplay Nothing

-- Color constants
red    = Color4 0.8 0.1 0.0 1.0
green  = Color4 0.0 0.8 0.2 1.0
blue   = Color4 0.2 0.2 1.0 1.0

-- Front of gear face
gearFront :: GLfloat -> GLfloat -> GLfloat -> GLfloat -> GLint -> GLint 
-> IO ()
gearFront r0 r1 w da n t = 
  do
    let angle = (fromIntegral n)*2.0*Main.pi/(fromIntegral t)
    vertex $ Vertex3 (r0*(cos angle)) (r0*(sin angle)) w
    vertex $ Vertex3 (r1*(cos angle)) (r1*(sin angle)) w
    if (n<t)
      then do
             vertex $ Vertex3 (r0*(cos angle)) (r0*(sin angle)) w
             vertex $ Vertex3 (r1*(cos (angle+3*da))) (r1*(sin 
(angle+3*da))) w
             gearFront r0 r1 w da (n+1) t
      else return ()
-- front side of teeth
teethFront :: GLfloat->GLfloat->GLfloat->GLfloat->GLint->GLint->IO ()
teethFront  r1 r2 w da n t =
  do
    if (n<t)
      then do
        let angle = (fromIntegral n)*2.0*Main.pi/(fromIntegral t)
        vertex $ Vertex3 (r1*(cos angle))          (r1*(sin angle))          
w
        vertex $ Vertex3 (r2*(cos (angle+da)))     (r2*(sin (angle+da)))     
w
        vertex $ Vertex3 (r2*(cos (angle+2.0*da))) (r2*(sin 
(angle+2.0*da))) w
        vertex $ Vertex3 (r1*(cos (angle+3.0*da))) (r1*(sin 
(angle+3.0*da))) w
        teethFront r1 r2 w da (n+1) t
      else
        return ()

--back side of gear
gearBack :: GLfloat -> GLfloat -> GLfloat -> GLfloat -> GLint -> GLint 
-> IO ()
gearBack r0 r1 w da n t =
  do 
    let angle = (fromIntegral n)*2.0*Main.pi/(fromIntegral t)
    vertex $ Vertex3 (r1*(cos angle)) (r1*(sin angle)) w
    vertex $ Vertex3 (r0*(cos angle)) (r0*(sin angle)) w
    if (n<t)
      then do
             vertex $ Vertex3 (r1*(cos (angle+3*da))) (r1*(sin 
(angle+3*da))) w
             vertex $ Vertex3 (r0*(cos angle)) (r0*(sin angle)) w
             gearFront r0 r1 w da (n+1) t
      else return ()

-- back side of teeth
teethBack :: GLfloat -> GLfloat -> GLfloat -> GLfloat -> GLint -> GLint 
-> IO()
teethBack r1 r2 w da n t = 
  do
    if (n<t)
      then do
        let angle = (fromIntegral n)*2.0*Main.pi/(fromIntegral t)
        vertex $ Vertex3 (r1*(cos (angle+3.0*da))) (r1*(sin 
(angle+3.0*da))) w
        vertex $ Vertex3 (r2*(cos (angle+2.0*da))) (r2*(sin 
(angle+2.0*da))) w
        vertex $ Vertex3 (r2*(cos (angle+1.0*da))) (r2*(sin 
(angle+1.0*da))) w
        vertex $ Vertex3 (r1*(cos (angle+da)))     (r1*(sin (angle+da)))     
w
        teethBack r1 r2 w da (n+1) t
      else
        return  ()

-- Outward faces of teeth */
teethFace :: GLfloat -> GLfloat -> GLfloat -> GLfloat -> GLint -> GLint 
-> IO ()
teethFace r1 r2 w da n t = 
  if (n<t) then do
    let angle = (fromIntegral n)*2.0*Main.pi/(fromIntegral t)
        nw    = (-w)
        u'    = r2*(cos (angle+da)) - r1*(cos angle)
        v'    = r2*(sin (angle+da)) - r1*(sin angle)
        len   = sqrt (u'*u'+v'*v')
        u     = u'/len
        v     = v'/len
        u2    = r1*(cos (angle+3.0*da))-r2*(cos (angle+2.0*da))
        v2    = r1*(sin (angle+3.0*da))-r2*(sin (angle+2.0*da))
    vertex $ Vertex3 (r1*(cos angle)) (r1*(sin angle)) w
    vertex $ Vertex3 (r1*(cos angle)) (r1*(sin angle)) nw
    currentNormal $= Normal3 v (-u) 0.0
    vertex $ Vertex3 (r2*(cos (angle+da))) (r2*(sin (angle+da))) w
    vertex $ Vertex3 (r2*(cos (angle+da))) (r2*(sin (angle+da))) nw
    currentNormal $= Normal3 (cos angle) (sin angle) 0.0
    vertex $ Vertex3 (r2*(cos (angle+2.0*da))) (r2*(sin (angle+2.0*da))) 
w
    vertex $ Vertex3 (r2*(cos (angle+2.0*da))) (r2*(sin (angle+2.0*da))) 
nw
    currentNormal $= Normal3 v (-u) 0.0
    vertex $ Vertex3 (r1*(cos (angle+3.0*da))) (r1*(sin (angle+3.0*da))) 
w
    vertex $ Vertex3 (r1*(cos (angle+3.0*da))) (r1*(sin (angle+3.0*da))) 
nw
    currentNormal $= Normal3 (cos angle) (sin angle) 0.0
    teethFace r1 r2 w da (n+1) t
  else do
    vertex $ Vertex3 r1 0.0 w
    vertex $ Vertex3 r1 0.0 (-w)

-- Inside Radius
gearInside         :: GLfloat -> GLfloat -> GLint -> GLint -> IO ()
gearInside  r w n t = 
  do
    let angle = (fromIntegral n)*2.0*Main.pi/(fromIntegral t)
    currentNormal $= Normal3 (-(cos angle)) (-(sin angle)) 0.0
    vertex $ Vertex3 (r*(cos angle)) (r*(sin angle)) (-w)
    vertex $ Vertex3 (r*(cos angle)) (r*(sin angle)) w
    if (n<t) then gearInside r w (n+1) t
             else return ()
     
-- Gear drawing routine
gear :: GLfloat -> GLfloat -> GLfloat -> GLint -> GLfloat -> IO ()
gear r0 o_radius width teeth depth = 
  do
    let r1 = o_radius - depth / 2.0::GLfloat
        r2 = o_radius + depth / 2.0::GLfloat
        da = 2.0 * Main.pi / (fromIntegral teeth) / 4.0::GLfloat
        w  = 0.5 * width
    shadeModel    $= Flat
    currentNormal $= Normal3 0.0 0.0 (1.0::GLfloat)
    renderPrimitive QuadStrip $ gearFront  r0 r1 w    da 0 teeth 
    renderPrimitive Quads     $ teethFront r1 r2 w    da 0 teeth 
    renderPrimitive QuadStrip $ gearBack   r0 r1 (-w) da 0 teeth 
    renderPrimitive Quads     $ teethBack  r1 r2 (-w) da 0 teeth 
    renderPrimitive QuadStrip $ teethFace  r1 r2 w    da 0 teeth
    shadeModel $= Smooth
    renderPrimitive QuadStrip $ gearInside r0 w 0 teeth
    return ()
-- Create the different gears
gear1 :: IO DisplayList
gear1  = defineNewList Compile $ do
           materialAmbientAndDiffuse Front $= red
           gear (1.0::GLfloat) (4.0::GLfloat) (1.0::GLfloat)
                (20::GLint) (0.7::GLfloat)

gear2 :: IO DisplayList
gear2  = defineNewList Compile $ do
           materialAmbientAndDiffuse Front $= green
           gear (0.5::GLfloat) (2.0::GLfloat) (2.0::GLfloat)
                (10::GLint) (0.7::GLfloat)

gear3 :: IO DisplayList
gear3  = defineNewList Compile $ do
           materialAmbientAndDiffuse Front $= blue
           gear (1.3::GLfloat) (2.0::GLfloat) (0.5::GLfloat)
                (10::GLint) (0.7::GLfloat)

printFPS      :: Frames -> IO ()
printFPS frame =
  do
    (f,et)  <- get frame
    ms      <- get elapsedTime
    if ((ms - et) > 5000)
      then 
        do
          let seconds = (fromIntegral (ms - et))/1000.0
              fps     = (fromIntegral f) / seconds
          putStr $ show f
          putStr " frames in "
          putStr $ show seconds
          putStr " seconds = "
          putStr $ show fps
          putStr " FPS\n"
          frame $= (0, ms)
      else
        frame $= (f+1, et)

-- Display event handling
display   :: (DisplayList,DisplayList,DisplayList) -> 
             Frames -> View -> IORef GLfloat -> IO ()
display (g1,g2,g3) frames viewRot angle =
  do
    clear [ColorBuffer,DepthBuffer]
    (x,y,z) <- get viewRot
    a       <- get angle
    printFPS frames
    preservingMatrix $ do
      rotate x $ Vector3 (1.0::GLfloat) 0              0
      rotate y $ Vector3 0              (1.0::GLfloat) 0 
      rotate z $ Vector3 0              0              (1.0::GLfloat) 
      -- Gear 1
      preservingMatrix $ do
        translate $ Vector3 (-3.0::GLfloat) (-2.0::GLfloat) 0  
        rotate a $ Vector3 0 0 (1.0::GLfloat) 
        callList g1
      -- Gear 2
      preservingMatrix $ do
        translate $ Vector3 (3.1::GLfloat) (-2.0::GLfloat) 0  
        rotate (-2.0 * a - 9.0) $ Vector3 0 0 (1.0::GLfloat) 
        callList g2
      -- Gear 3
      preservingMatrix $ do
        translate $ Vector3 (-3.1::GLfloat) (4.2::GLfloat) 0  
        rotate (-2.0 * a - 25.0) $ Vector3 0 0 (1.0::GLfloat) 
        callList g3
    swapBuffers  

-- Keyboard event handling (modify view or exit)
keyboard :: View -> Key -> KeyState -> Modifiers -> Position -> IO ()
keyboard view c _ _ _ = keyForPos view c

keyForPos                             :: View -> Key -> IO ()
keyForPos _       (Char 'q')           = exitWith ExitSuccess
keyForPos _       (Char 'Q')           = exitWith ExitSuccess
keyForPos viewRot (Char 'z')           = modRot viewRot (id,     id,    
\x->x-5)
keyForPos viewRot (Char 'Z')           = modRot viewRot (id,     id,     
(+)5)
keyForPos viewRot (SpecialKey KeyLeft) = modRot viewRot (id,     (+)5,   
id)
keyForPos viewRot (SpecialKey KeyRight)= modRot viewRot (id,     
\x->x-5,id)
keyForPos viewRot (SpecialKey KeyUp)   = modRot viewRot ((+)5,   id,     
id)
keyForPos viewRot (SpecialKey KeyDown) = modRot viewRot (\x->x-5,id,     
id)
keyForPos _       (Char c)             = if (c == (chr 27)) 
                                           then exitWith ExitSuccess
                                           else return ()
keyForPos _ _ = return ()

modRot                   :: View -> ViewFunc -> IO ()
modRot viewRot (fx,fy,fz) = do
                              (x,y,z) <- get viewRot
                              viewRot $= (fx x, fy y, fz z)
                              postRedisplay Nothing
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.