Usage Tutorial
==============
This page should serve as tutorial and also as reference to Ruby bindings for OpenGL
language. It is assumed that you have basic understanding of both OpenGL and Ruby.
If you are new to OpenGL, you can start by visiting [OpenGL homepage](http://www.opengl.org)
, reading the [OpenGL Programming Guide](http://opengl.org/documentation/books/#the_opengl_programming_guide_the_official_guide_to_learning_opengl_version) (also known as Red Book) or going to [NeHe's tutorials page](http://nehe.gamedev.net/).
If you are new to Ruby, [the ruby-lang website](http://www.ruby-lang.org/en/documentation/) contains lots of
documentation and manuals for Ruby.
Table of Contents
==============
Basics:
* [Naming Conventions](#naming_conventions)
* [Function parameters](#function_parameters)
* [Return values](#return_values)
* [Matrices](#matrices)
* [Textures and other raw data](#textures)
* [Error Checking](#error_checking)
* [Examples](#examples)
Advanced stuff:
* [OpenGL version and Extensions](#extensions)
* [Selection and Feedback queries](#selection_feedback)
* [Vertex Arrays](#vertex_arrays)
* [Buffer Objects](#buffer_objects)
* [GLUT, SDL, GLFW..](#glut_sdl)
* [GLUT callbacks](#glut_callbacks)
* [Internals](#internals)
API reference:
* TODO
Naming conventions
------------------
The bindings contains three modules:
* 'Gl' - OpenGL functions itself
* 'Glu' - OpenGL Utility Library API - higher-level drawing routines, NURBS etc.
* 'Glut' - OpenGL Utility Toolkit - low level functions such as creating OpenGL
context, opening window or handling user input
You can import all three modules by calling
{{ruby}}
require 'opengl'
You can also load the modules separately by using:
{{ruby}}
require 'gl'
require 'glu'
require 'glut'
The functions and constants are named the same as their C counterparts:
{{ruby}}
require 'opengl'
...
Gl.glFooBar( Gl::GL_FOO_BAR )
Glu.gluFooBar( Glu::GLU_FOO_BAR )
Glut.glutFooBar( Glut::GLUT_FOO_BAR )
This is the 'full' syntax, usefull when you are expecting name clashes
with other modules, or just want to be formal ;) More often, you will
want to use the 'C-style' syntax, which you can accomplish by using 'include'
to export the module functions and constants to global namespace:
{{ruby}}
require 'opengl'
include Gl,Glu,Glut
...
glFooBar( GL_FOO_BAR )
gluFooBar( GLU_FOO_BAR )
glutFooBar( GLUT_FOO_BAR )
Finally, you can use the 'old' syntax:
{{ruby}}
require 'opengl'
...
# Note the missing prefixes in functions and constants
# and also capitalization of module names
GL.FooBar( GL::FOO_BAR )
GLU.FooBar( GLU::FOO_BAR )
GLUT.FooBar( GLUT::FOO_BAR )
This syntax was used by previous ruby-opengl versions; some people also
consider it as being more in the spirit of OO programming. It has one
downside though - due to Ruby's naming scheme, you cannot use constants
which begins with number, e.g. GL_2D would under this syntax be (GL::)2D
which is illegal.
All three variants of syntax will continue to be supported in future,
so it's up to you which one you choose to use.
The rest of this tutorial will use the C syntax.
Calling syntax
--------------
Function parameters
--------------
For most types the ruby syntax follows the C API. If needed, ruby will do
automatic parameter conversion to required type if possible. Example:
{{ruby}}
glVertex3f( 1.0, 1.0, 1.0 ) # matches C syntax
glVertex3f( 1, 1, 1 ) # equivalent to the above
glVertex3f( "string", 1, 1 ) # raises TypeError exception
Arrays are passed/received as Ruby arrays:
{{ruby}}
vertex = [ 1, 1, 1 ]
glVertex3fv( vertex )
For functions with multiple parameter-number variations (glVertex,glColor,...)
we define 'overloaded' functions, as in:
{{ruby}}
glVertexf( 1, 1 ) # will call glVertex2f()
glVertexf( 1, 1, 1 ) # will call glVertex3f()
glVertexf( 1, 1, 1, 1 ) # will call glVertex4f()
glVertexi( 1, 1 ) # will call glVertex2i()
...
# and so on
Return values
-------------
In C, OpenGL functions rarely return values directly, instead you pass in pointer to
preallocated buffer and they will fill it with values; sometimes you have to even query
how big buffer you'll need to allocate. Ruby does this all for you, returning either single
value or array:
{{ruby}}
glColor4f( 1.0, 1.0, 1.0, 1.0 )
...
color = glGetDoublev(GL_CURRENT_COLOR)
p color # will be [1.0,1.0,1.0,1.0]
Matrices
-------------
Matrices are passed and received as ruby array, or as ruby Matrix objects:
{{ruby}}
matrix_a = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
matrix_b = [ [ 0, 1, 2, 3 ],
[ 4, 5, 6, 7 ],
[ 8, 9,10,11 ],
[ 12,13,14,15 ] ]
matrix_c = Matrix.rows( [ [ 0, 1, 2, 3 ],
[ 4, 5, 6, 7 ],
[ 8, 9,10,11 ],
[ 12,13,14,15 ] ] )
...
glLoadMatrixf(matrix_a)
glLoadMatrixf(matrix_b)
glLoadMatrixf(matrix_c) # same result
You may also create your own matrix class and pass it this way, provided that it
is convertible to array (has 'to_a' method).
Note that as OpenGL uses column-major
notation for matrices, you may need to call transpose() when working with
row-major matrices or arrays in ruby.
Textures and other raw data
-------------
Data for textures, arrays, buffers etc. can be specified either as ruby arrays or directly as raw packed strings -
strings that contains their direct memory representation (just like C arrays). If you need to convert between
ruby arrays and these strings, use ruby Array#pack() and String#unpack() functions.
Example:
{{ruby}}
# create texture, 2x2 pixels,
# 3 components (R,G,B) for each pixel as floats
texture = [
1.0, 0.0, 0.0, # 1st pixel, red
0.0, 1.0, 0.0, # 2nd pixel, green
0.0, 0.0, 1.0, # 3rd pixel, blue
1.0, 1.0, 1.0 # 4th pixel, white
]
# convert it to string
# f = native float representation
# * = convert all values in the array the same way
data = texture.pack("f*")
...
glTexImage2D(
GL_TEXTURE_2D, # target
0, # mipmap level,
GL_RGB8, # internal format
2, 2, # width, height
0, # border = no
GL_RGB, # components per each pixel
GL_FLOAT, # component type - floats
data # the packed data
)
Reverse works just the same:
{{ruby}}
...
data = glGetTexImage( # returns the packed data as string
GL_TEXTURE_2D, # target
0, # mipmap level
GL_RGB, # components per pixel
GL_FLOAT # component type
)
# now convert it to ruby array
texture = data.unpack("f*")
...
For storage, packed strings are more memory efficient than ruby arrays, but
cannot be easily changed or manipulated.
Error Checking
--------------
Starting with version 0.60.0, ruby-opengl performs automatic checking of OpenGL and GLU errors.
Functions:
{{ruby}}
Gl.enable_error_checking
Gl.disable_error_checking
Gl.is_error_checking_enabled? # true/false
When the checking is enabled (default), glGetError() is executed after each OpenGL call, and should error
occur, Gl::Error exception is raised:
{{ruby}}
Gl.enable_error_checking
...
begin
...
glEnable(GL_TRUE) # will raise exception
...
rescue Gl::Error => err
# err.id contains the OpenGL error ID
if (err.id == GL_INVALID_ENUM)
puts "Oh noes! You used invalid enum!"
...
end
...
end
Some GLU functions may also throw Glu::Error - the handling is the same as above.
It is usually good idea to leave error checking on for all your code, as OpenGL errors have habit to pop-up in
unexpected places. For now there is no measurable performance hit for error checking, although this may depend
on your graphic drivers implementation.
The Examples
-----------
Various examples are in 'examples' directory of the bindings. To run them, manually pass them to `ruby` like:
ruby some_sample.rb
On windows, you may want to use 'rubyw' instead, which displays the standard output window
as some examples use the console for usage info etc.
If you get 'opengl not found' error, and you installed ruby-opengl from gems, your
shell or ruby installation is probably not configured to use the gems; in that case type:
ruby -rubygems some_sample.rb
The `README` file in the `examples` directory contains some notes on the examples.
OpenGL Version and Extensions
-----------
To query for available OpenGL version or OpenGL extension, use Gl.is_available? function:
{{ruby}}
# true if OpenGL version is 2.0 or later is available
Gl.is_available?(2.0)
...
# returns true if GL_ARB_shadow is available on this system
Gl.is_available?("GL_ARB_shadow")
For list of what extensions are supported in ruby-opengl see this [page](extensions.html)
The extensions' function names once again follows the C API. Some extensions were over time
promoted to ARB or even to OpenGL core, retaining their function names just with suffix changed
or removed. However sometimes the functions semantics was changed in the process, so to avoid
confusion, ruby-opengl bindings will strictly adhere to the C naming, e.g. :
{{ruby}}
# will call the function from GL_ARB_transpose_matrix extension
glLoadTransposeMatrixfARB(matrix)
...
# will call the function from OpenGL 1.3
glLoadTransposeMatrixf(matrix)
Note: ruby-opengl is compiled against OpenGL 1.1, and all functions and enums from later
versions of OpenGL and from extensions are loaded dynamically at runtime. That means that all
of OpenGL 2.1 and supported extensions are available even if the ruby-opengl bindings are
compiled on platform which lacks proper libraries or headers (like for example Windows without
installed graphic drivers). This should ease binary-only distribution and application packaging.
Selection/Feedback queries
-----------
Querying selection and feedback is different from C. Example:
{{ruby}}
# this will create selection buffer 512*sizeof(GLuint) long
buf = glselectbuffer(512)
# enter feedback mode
glRenderMode(GL_SELECT)
... # draw something here
# return to render mode
count = glRenderMode(GL_RENDER)
# at this point the buf string is freezed and contains
# the selection data, which you can recover with unpack
# function
data = buf.unpack("I*") # I for unsigned integer
# also, next call to glRenderMode(GL_SELECT) will overwrite
# the 'buf' buffer with new data
The feedback query follows the same pattern, only the data are stored
as floats.
Vertex Arrays
-----------
In current state, vertex arrays are not very efficient in ruby-opengl, as it is not possible to change
the array content once it is specified, and there is overhead for converting between ruby and C representation
of numbers. Using display lists for static and immediate mode for dynamic objects is recommended instead.
You can specify the data the same way as [texture data](#textures). Example:
{{ruby}}
normals = [0,1,0, 1,0,0, 1,1,1]
glNormalPointer(GL_FLOAT,0,normals)
...
glEnable(GL_NORMAL_ARRAY)
glDrawArrays(...)
...
This applies to all *pointer functions. glGetPointerv will return reference to the frozen string
previously specified.
Buffer Objects
-----------
Once again, in current state buffer objects (VBOs in particular) are not very efficient in ruby-opengl.
Unlike textures and vertex arrays, the data for buffers *must* be prepacked by using .pack() function,
as buffers does not retain information about the storage type. Mapping of the buffer afterwards is read-only.
Like in C, buffer binding affects some functions in way that if particular buffer is bound, the related
functions (for example glTexImage) take integer offset in place of data string argument. This is also true
for getter functions (e.g. glGetTexImage) - instead of returning the data string, they take offset as they're
last argument (so in ruby they take one extra argument), and will write the data in the bound buffer as expected.
VBO example:
{{ruby}}
# specify 3 vertices, 2*float each
data = [0,0, 0,1, 1,1].pack("f*")
...
# generate buffer name
buffers = glGenBuffers(1)
# bind to the name to ARRAY buffer for vertex array
glBindBuffer(GL_ARRAY_BUFFER,buffers[0])
# here the data is specified, size is n*sizeof(float)
# note that you don't get to specify type, as buffers
# operate on byte level
glBufferData(GL_ARRAY_BUFFER,6*4,data,GL_DYNAMIC_DRAW)
...
# here instead of specyfing the data, you pass '0' (or
# positive integer) as offset to the bound buffer
glVertexPointer(2,GL_FLOAT,0,0)
...
glEnableClientState(GL_VERTEX_ARRAY)
...
GLUT, SDL, GLFW..
---------
When it comes to low-level task like GL window creation, input and event handling, the first choice is GLUT,
as it is readilly available alongside OpenGL. However both GLUT itself and its implementations
have their drawbacks, and for that and other reasons there are number of replacement libraries.
You can use any of them with ruby-opengl (as long as there are ruby bindings for them).
Here is example for [SDL](http://www.kmc.gr.jp/~ohai/index.en.html):
{{ruby}}
require 'opengl'
require 'sdl'
# init
SDL.init(SDL::INIT_VIDEO)
SDL.setGLAttr(SDL::GL_DOUBLEBUFFER,1)
SDL.setVideoMode(512,512,32,SDL::OPENGL)
...
Gl.glVertex3f(1.0,0.0,0.0)
...
SDL.GLSwapBuffers()
...
and another example for [GLFW](http://ruby-glfw.rubyforge.org/):
{{ruby}}
require 'opengl'
require 'glfw'
# init
Glfw.glfwOpenWindow( 500,500, 0,0,0,0, 32,0, Glfw::GLFW_WINDOW )
...
Gl.glVertex3f(1.0,0.0,0.0)
...
Glfw.glfwSwapBuffers()
...
GLUT callbacks
--------------
The GLUT callback functions are specified as Proc objects, which you can
either create with lambda as:
{{ruby}}
reshape = lambda do |w, h|
...
end
...
glutReshapeFunc( reshape )
or by conversion from normal functions:
{{ruby}}
def reshape(w,h)
...
end
...
glutReshapeFunc( method("reshape").to_proc )
Note: An older notation you'll see instead of `lambda` is `proc`. The
PickAxe v2 notes that `proc` is "mildly deprecated" in favor of `lambda`.
You'll also sometimes see `Proc.new` used in place of either. Pages 359-360 of
PickAxe v2 describe the differences between using `lambda` and `Proc.new`,
but for our purposes either will be fine.
Internals
---------
The directory structure follows current Ruby standards, with a few
extra directories added.
* `doc/` -- Contains documentation for the project (from which this
website is generated).
* `examples/` -- Example programs.
* `ext/` -- Contains subdirectories, one for each of the three extension
modules (gl, glu, glut). Herein are the files needed to compile the extension
modules.
* `lib/` -- Files that the user is meant to `require` in their own code.
* `test/` -- Contains automatic testsuite for the bindings
* `utils` -- Some utility scripts used to help generate code, documentation
and website.
* `website` -- After running `rake gen_website` this directory will contain
the ruby-opengl website.