Spifftastic,

 

Ruby OpenGL Experiments

One of my complaints in working with Ruby is its lack of anything for doing graphical work, be it UI toolkits – most of which are now unmaintained – or simply graphics in general, like OpenGL. The title of this post should tell you which direction this is going. I could complain that Ruby’s image is taken over by the Rails folks or that web developers ruin the fun for everyone else, but neither of these complaints have any objective basis and are just for fueling my personal biases against web developers. So, let’s talk about Ruby, OpenGL, and what I did to make these two unsafe things work together.

OpenGL

The current state of OpenGL in Ruby is decent-ish. There are a handful of gems for Ruby support, some for Ruby 1.9 and at least two for Ruby 2.0. It’s a good situation. Your options are, for the most part, as follows:

  • ruby-opengl2, ruby-opengl, and opengl — Fundamentally the same gems, though for different Ruby versions. They all provide the GL API versions 1.0 through 2.1. Not the best choice, but there are folks out there trying to make sure it still works, and if nothing else, it’s maintained in that respect. If you’re still using 1.8 or 1.9, it might be up your alley.
  • opengl3 — A GL 2.1 - 4.2 gem implemented using FFI, which makes it pretty useful across interpreters (it should work with JRuby, for example). The downside is that it is large and overly complicated, which is strange. Looks like it might’ve been written by someone whose only other language is Java. It’s still being worked on though, so I’m sure it’s not going anywhere for a while.
  • opengl-core — This is my gem. It only supports the OpenGL core profile and extensions, so it’s purely 3.2 and up. Like ffi-opengl (a defunct 3 year-old gem that’s dead) and opengl3, this uses FFI to load GL. Unlike opengl3, it doesn’t have that much code in it except under the opengl-core/aux features, which aim to wrap some GL functionality in slightly more Ruby-friendly code. By default, it is straight OpenGL with none of the difficult-to-work-with-in-Ruby aspects hidden. The downside to opengl-core is that I’ve only built it for Ruby 2.x, so for those of you stuck on 1.9 for whatever reason, it may not work.

So bindings for OpenGL are available and usable now, which is a far sight better than things were a year ago. For the purpose of this blog post onward, I’ll only talk about opengl-core because I wrote it and therefore I use it. In other words, if you want to use one of the other gems, you’ll have to consult someone else. If you’re not using Ruby 2.x, you might not find this very informative (so go install Ruby 2.x and join the future).

opengl-core is, put simply, a very small amount of handwritten code and a very large amount of generated code. Almost all of opengl-core is defined through its gl_commands.rb and gl_enums.rb scripts, since both are used to sort of statically define all GL commands at runtime. That way there’s not too much need for eval or define_method or what have you, as the method stubs are already there and forward calls to the appropriate function (and, if not loaded, they make sure that happens too). So when you call a function for the first time, it’s loaded then and there, unless you told the gem to load everything ahead of time (Gl.load_all_gl_commands! is a thing).

So why write my own OpenGL gem when opengl3 and ruby-opengl2 et al. exist? Well, I didn’t like the former’s code. Again, looks like it was written by someone who knows only Java — it’s honestly a really strange piece of code to look at when you consider how simple it is to load a GL function. Very over-engineered. But anyway, its developer likely had much different needs than I, so it’s hard to say why it is the way it is without asking ‘em. Either way, opengl3 doesn’t suit my needs. ruby-opengl2 et al., on the other hand, only works up to GL 2.1, which I don’t code for. Period. Easy decision.

So, if you want opengl-core, you just install it via

$ gem install opengl-core

GLFW 3

Another problem I encountered was that there were no GLFW 3 bindings for Ruby. This won’t surprise anyone — Camilla Berglund (aka elmindreda) recently released GLFW 3. It seems obvious that nobody would get around to it in such little time.1 So, there were two options: use another library or write the gem myself.

There were a few other options, like SDL, GLUT, and GLFW 2.7. Using SDL is an alright idea, but there were no SDL 2 bindings for Ruby. The other bindings for 1.2 are plentiful but not really what I was looking for. GLUT is the spawn of Satan and the less we have to deal with it, the better. That it somehow perpetuates itself is mysterious, but I suppose there will always be the old OpenGL tutorials telling people they should use it.

GLFW 2.7 also has bindings, except that its gem, ruby-glfw, doesn’t work on Ruby 2.x, so that made it much less appealing. I’d been asked, previously, by Tom Black about whether I would be willing to help write GLFW bindings for Ruby 2.x, but at the time I was mostly concerned about the time needed to do the GLFW and OpenGL gems and so on, mostly because the work needed to do anything in Ruby would be a bit extensive. I think he might have contacted me because I hold a fork of it on GitHub and once tried to update it. Either way, I didn’t start on either until quite some time after, and I believe he’d found his own solutions.

The «do it yourself» option was then the only one that didn’t involve writing my own window-handling code. That’s something I could’ve done, at least for OS X (it’s remarkably simple), but it’s not a great idea. So, I sat down and wrote the GLFW 3 bindings for Ruby, developing it as a C extension rather than using FFI because that simplified a lot of the Ruby/C compatibility issues. Turns out Ruby’s C API is very nice, but very undocumented and very heavy on the abbreviations. You get used to seeing things like rb_str_new2 and SIZET2NUM (size_t → Numerical2).

For the most part, I tried to make sure the Ruby API for GLFW 3 was similar to its C API without becoming unbearable to use in Ruby. So, Windows and Monitors are their own objects, and GLFW is one big module encompassing them and the constants needed. It’s nice, since you can now create a window and main loop by just writing something like this:

#!/usr/bin/env ruby
require 'glfw3'

Glfw::init

main_window = Glfw::Window.new(800, 600, "Window Title")
raise "Unable to create window" if main_window.nil?

main_window.close_callback = lambda { |window| window.should_close = true }
main_window.make_context_current

until main_window.should_close?
  Glfw::wait_events

  # Do drawing stuff here. Also clear the backbuffer because otherwise
  # you're going to see a lot of junk when swapping buffers.

  main_window.swap_buffers
end

main_window.destroy
Glfw::terminate

So that’s about twelve to fourteen (depending on how you space out your lambda) lines. I think that’s pretty good. I actually fixed a small bug in Glfw::terminate that I hadn’t noticed before as a result of writing out this post, but otherwise there’s not too much to say there. The bindings are easy to use, they’re Ruby-friendly, and they’re out there in the glfw3 gem. Contrary to the repo names I use, which don’t correspond to gem names, the gem is just called glfw3 and you can install it via gem using the following:

$ gem install glfw3

The 3D Math Problem

A problem I greatly enjoy complaining about and solving several times over is that, because OpenGL’s meager math functions were deprecated, we’re always in need of code to handle the 3D maths side of things for us.

At first I wondered how it’d work if 3D maths were implemented in Ruby, so I tried doing a simple 4x4 matrix multiplication — work you’ll need to do reasonably often — in both Ruby and via a C extension and ran it through a profiler. For folks who aren’t doing very much, a Ruby implementation of 3D maths is probably sufficient, but it’s also very, very slow even when you try to reduce allocations, method calls, etc., for that operation.

On average, with my system, 4x4 matrix multiplication is 7x slower than a C extension. Both take a small amount of time, though it’s not as negligible if you’re doing a lot of it and have only 16 milliseconds to do it in. Keep in mind that, for the C extension, the Ruby values have to be converted to C values, Ruby has to forward all the arguments to the C function, and so on, and it’s still 7 times faster. So, if you need fast math code, always write it in C. If you need accurate but slow maths code, you can probably safely write it in Ruby (in which case, you’ll probably use a class like BigDecimal).

So, that led me to take my old Snow-Palm 3D maths code, add some bits to it, and write bindings around it for Ruby and call the result snow-math. Now it’s possible, for the most part, to get somewhat fast 3D maths. Plus it all works with GL since the 3D math types are all stored as contiguous blocks of memory, meaning you don’t have to pack them as strings before sending them off to GL (e.g., [1, 2, 3].pack('fff') to convert an array to three floats).

So, to continue the trend of telling you how to install something, you can install snow-math via gem using the following:

$ gem install snow-math

I’d also recommend checking the README on GitHub because there are some options to customize how the extension gets compiled, like whether to use floats instead of doubles.3 Most common types are covered by the library, namely two-, three-, and four-component vectors, quaternions, and 3x3 and 4x4 matrices. Planes and rays are not covered, unfortunately, though they should be easy to implement using the types provided.


That about sums up all but one odd experiment I did, snow-data — a library for defining a layout for blocks of memory and so on, similar to various C-struct libraries for Ruby (snow-data being one). I won’t write about that here though, as it’s not entirely relevant to OpenGL in Ruby (though you could use it to describe vertices and so on).

At any rate, OpenGL in Ruby is better than it’s been in a long time, and it’s now pretty reasonable to write applications, games, demos, tools, and so on using OpenGL in Ruby. Considering the flexibility of the language and what it offers for anyone who wants to worry more about their programs’ functionality than the boilerplate details, it’s a decent language for the job.

The Gems

If you’d like a list of the gems I made again, here you go:


  1. Especially not when most Ruby people are hideous web developers with no care for doing awesome things with Ruby. [return]
  2. This conversion is missing from JRuby’s C API, but then JRuby deprecated it and is still stuck on 1.9, so screw them. In general, JRuby’s C API is a barren wasteland of misery, so I don’t recommend bothering with it. If you need bindings that are JRuby-compatible, always go with FFI. It’ll probably be faster by going through the JVM as well. [return]
  3. snow-math uses doubles by default because Ruby also uses doubles internally. So, it results in the least amount of casting at the cost of allocating twice as much memory per type. [return]