Compile-time Raytracer and C++ Metaprogramming

As you’ve probably derived from the title the two raytraced images above are computed during compilation, I thought it was quite cool so I thought I’d write a short blogpost on C++ template metaprogramming. To give you a brief overview here, the idea is when the compiler is invoked on the code is to have it evaluate as many expressions as possible. So during compilation all of the ray bounces ect are calculated and then the resulting code is just a massive array of floats with the value at each pixel represented by a constant value. This array is saved to a file when you run the executable.

It is quite tricky to debug however, so you can see some artifacts in the images above. They cropped up just before started writing this post and I’ve already spent waaaay more time than I intended on this mini-project so I’m just cutting my losses with them. I have another picture below from an earlier build which doesn’t have the fuzzy effect (but does have that *one dot*, which is annoying).

Early version, ground reflection, no weird fuzzy effect.

Take this example:

template<int base,int power>
struct Pow
const static int result = Pow<base,power-1>::result*base;

template<int base>
struct Pow<base,0>
const static int result = 1;

int main(int argc,char *argv[])
return Pow<5,2>::result;

If we look into the assembly file produced we just see the constant 25 being written to a register.

 mov eax, 25 

The total assembly file is only 22 lines (function version is >200 on -O2)! Here’s what happened,  Pow was created with the template parameters of 5 and 2, this doesn’t call a function with those values it creates a type using them. This type has a single constant value. When Pow<base,power-1> is evaluated it creates a whole new type with its own constant static field which creates another, and so on until power is zero.

template<int base>

struct Pow<base,0> {};

This is called a template specialisation, it allows for the initial template to be overridden when the conditions are met. As the ‘base’ value is itself templated it can be any value, but the second value, power, is specified to be zero. This means when an instance of ‘Pow’ is created using any value of base and a value of zero for power, it compiles this template instead of the base one. In our case it just returns the value one and as that is a constant value its multiplication with the constant base value in Pow<base,1> is immediately evaluated and so on until it reaches the power which the first struct was created with, and with that you will be left with one single constant value. How do you make a Raytracer out of this? With modest difficulty, to start with anyway.  Basically, you just stack all of these constant evaluations on top of each other. This results in some pretty messy code, here is the mess for Vector Dot product (Scalar is a namespace, fyi): Screen Shot 2016-02-17 at 03.09.53

“Why do you need Scalar::Multiply if you just multiplied ints before?”

Well. Raytracers don’t work so well with ints so you need to use floats, but as it turns out this is quite tricky when it comes to templates, recall how we used ‘int’ and now we use ‘class’ (which just means generic type name, in this case), well that only works for constant integral types. So we need to do some trickery to wrap float values in integral types. To get around this I scale up the floats by some huge number (65536)  and store them in a int64_t (I didn’t invent this! I just saw it in another compile time tracer,  you can see his tracer here). Great thing about this is you only have to do some really basic stuff for division and multiply, and subtraction and addition work “out of the box”.

static const int64_t scale = (1<<16);

template<int64_t v>
struct Number
static const int64_t val = v;
constexpr static const float actual_value = static_cast<float>(v) / static_cast<float>(scale);

Once we’ve written the basic types, (Scalar and Vector) plus the normal operations and some other utility functions, like “IF” (see below), then we just stack normal raytracing maths on top of each other. This post is about the compile-time part, so I won’t go into the raytracing maths.Screen Shot 2016-02-21 at 17.22.42

This was the first time I had tinkered with metaprogramming so it was pretty hard at first, basically once one template evaluation fails you get a huge chain of thousands of failures.  At the start I probably had a failure about 80% of the time I changed something non-trivial, so it took a long time. But, once you get used to this way of programming it starts to flow quite well and it becomes much more intuitive.

This has been a pretty cool project, learnt loads as usual. When I started out I was going to try to recreate a scene from an earlier OpenCL Raytracer I had written, which can be seen in action here. That ran in realtime, this one takes probably around 15-20 minutes to calculate for a 300*300 image and I have quite a few projects on concurrently so I can’t really afford to run it on larger scenes. Having said that, I still think it’s pretty cool having a Raytraced image produced during compilation! So I’m still pleased with it. I’ll end this with a shot from my earlier Raytracer for comparison, cya next time!

Shot from my old OpenCL Raytracer

One thought on “Compile-time Raytracer and C++ Metaprogramming

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s