Cylinders, cones, and parallelism

I wrote up a blog post on the Ray Tracer Challenge book by Jamis Buck, exploring how over xmas I spent some odd moments over the xmas/new year break working on an old-school style ray tracer, partly for fun and partly as a way to exercise my OCaml muscles before they totally atrophy. Despite having more important things to do I still found time to plug away at this, as it's been good to use OCaml on another bigger project and see how different features of the language click a little more, and I get to make some pretty pictures along the way:

An image of a ray traced scene. A series of pale, over a pale background rainbow coloured tubes are coming in from around the edges to the centre of the window radially, but not meeting in the middle, leaving a circular void.

It's not even the large features that are jumping out, it's appreciating the how/why of smaller language details, such as the local scoping that means rather than write:

let s = Shape.v Shape.Cube in

You can more succinctly write:

let s = Shape.(v Cube) in

It's one of those language features I knew about, but didn't really click with me, until I was writing types that had their own dependant constants. Then because I saw how I'd got here, rather than seeing it as weird syntax other libraries used (TSDL, the OCaml SDL wrapper I use does this a lot), it just made sense. I think this is why exercises like this are so important to me: reading about something doesn't stick nearly as well as when I hit an issue myself in practice.

Because I'm still working through the book, I've not put much effort into optimising my ray tracer: I know the later chapters touch a little on this, and I've still yet to complete all the primitives, so optimisations can wait. But now I'm playing with transparency and refraction the rendering is getting rather slow, so I did at least enable more brute force by using OCaml's Domains feature, aka, parallel multithreading. As a simple first pass I just throw each line in the output image into a queue and let the workers churn through them, which has the fun side effect that you can watch the cores race each other:

After dealing with Python multiprocessing it's been nice to have actual shared memory parallelism again, but still with features like queues to do memory safe communication between threads, something that I'm very familiar with thanks to my time with Grand Central Dispatch and Go.

I also spent a bit of time refactoring my code so that it is now a library such that I can have many test programs for different scenes, structured a bit like I did with Claudius, and I added the ability to save things to PNGs so I don't have to have the titlebar in all my screenshots.

Tags: weeknotes, ocaml, graphics, testing