Recently I am interested in game engine and start reading the articles with code in GameEngineFromScratch. Due to the “from scratch”, a self designed math library would never be ignored. In vector math computing, a handy feature called “swizzling” are widely used.

What is “swizzling”

The two most popular rendering APIs, OpenGL and DirectX, both provide GPU computing acceleration. For using GPU for computation, we need to write our computation code in provided shader language: GLSL for OpenGL and HLSL for DirectX. These languages both support “swizzling” feature for vector computing. A simple samples of swizzling is shown below:

1
2
3
4
5
6
Vec2d vecA(1.0, 2.0);   // should support multiple type like, int, float or double
Vec2d vecB, vecC;
vecB.yx = vecA; // vecB = (2.0, 1.0)
vecC = vecA.yx; // vecC = (2.0, 1.0)
...
// Should also work for Vec3, Vec4 ...

Since this feature is handy in vector computing, we are going to implement it in C++.

Basic swizzling in template

Because we are implementing vector for multiple base type (int, float, etc.), it first come with template. So let’s first implement swizzling of a 2d vector type with supporting on different base types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
template<template<typename> class TT, typename T, int A, int B>
class swizzle{
T v[2];
public:
TT<T>& operator=(const TT<T>& rhs)
{
v[A] = rhs.x;
v[B] = rhs.y;
return *(TT<T>*)this;
}
operator TT<T>() const
{
return TT<T>(v[A], v[B]);
}
};

template <typename T>
struct Vector2Type
{
union {
T data[2];
struct {T x, y;};
swizzle<Vector2Type, T, 0, 1> xy;
swizzle<Vector2Type, T, 1, 0> yx;
};
Vector2Type<T>() {};
Vector2Type<T>(const T& _v) : x(_v), y(_v) {};
Vector2Type<T>(const T& _x, const T& _y) : x(_x), y(_y) {};
operator T*() { return data; };
operator const T*() const { return static_cast<const T*>(data); };
};

typedef Vector2Type<int> Vec2i;
typedef Vector2Type<float> Vec2f;
typedef Vector2Type<double> Vec2d;

Here we use “template of template” for the swizzle class, because our 2-dimensional vector type need to use the type for basic value.

Swizzling for multi-dimension vector classes

The code above looks so far so good. However, consider about implementing 3d or 4d vector, there would be swizzle3d, swizzle4d, …, etc. which, make the code like a mess. In C++11, we could resolve this problem using template parameter pack.

The parameter pack makes us to declare one type of parameter without number constrains. It mostly used in function arguments and multiple arguments in template. Below is a small sample of variadic functions modified from here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <cstdarg>

double average(int count, ...)
{
va_list ap;
double total = 0;
va_start(ap, count); // Requires the last fixed parameter (to get the address)
for(int j=0; j<count; ++j)
// Requires the type to cast to.
// Increments ap to the next argument.
total += va_arg(ap, double);
va_end(ap);
return total/double(count);
}

Now let’s use template parameter pack to implement swizzle for multi-dimensional vectors. The code looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
template<template<typename> class TT, typename T, int ... Indexes>
class swizzle{
// here sizeof...() is a function to query the number of parameter pack
T v[sizeof...(Indexes)];
public:
TT<T>& operator=(const TT<T>& rhs)
{
int indexes[] = { Indexes... }; // unpack
for (int i = 0; i < sizeof...(Indexes); i++) {
v[indexes[i]] = rhs[i]; // access pack element
}
return *(TT<T>*)this;
}
operator TT<T>() const
{
// unpack
return TT<T>(v[Indexes]...);
}
};

// --------------------------
// Define multi-dimensional vector type
// --------------------------
template <typename T>
struct Vector2Type
{
union {
T data[2];
struct {T x, y;};
swizzle<Vector2Type, T, 0, 1> xy;
swizzle<Vector2Type, T, 1, 0> yx;
};
Vector2Type<T>() {};
Vector2Type<T>(const T& _v) : x(_v), y(_v) {};
Vector2Type<T>(const T& _x, const T& _y) : x(_x), y(_y) {};
operator T*() { return data; };
operator const T*() const { return static_cast<const T*>(data); };
};

// Vector3d
template <typename T>
struct Vector3Type
{
union {
T data[3];
struct {T x, y, z;};
swizzle<Vector2Type, T, 0, 1> xy;
swizzle<Vector2Type, T, 1, 0> yx;
swizzle<Vector2Type, T, 0, 2> xz;
swizzle<Vector2Type, T, 2, 0> zx;
swizzle<Vector2Type, T, 1, 2> yz;
swizzle<Vector2Type, T, 2, 1> zy;
swizzle<Vector3Type, T, 0, 1, 2> xyz;
swizzle<Vector3Type, T, 1, 0, 2> yxz;
swizzle<Vector3Type, T, 0, 2, 1> xzy;
swizzle<Vector3Type, T, 2, 0, 1> zxy;
swizzle<Vector3Type, T, 1, 2, 0> yzx;
swizzle<Vector3Type, T, 2, 1, 0> zyx;
};
Vector3Type<T>() {};
Vector3Type<T>(const T& _v) : x(_v), y(_v), z(_v) {};
Vector3Type<T>(const T& _x, const T& _y, const T& _z) : x(_x), y(_y), z(_z) {};
operator T*() { return data; };
operator const T*() const { return static_cast<const T*>(data); };
};

// Vector4d...

At the end

Template is really really powerful for creating efficient library.

The remaining part of our math library is filling various operator computations. These operation could be simply written in C++, or we can use single instruction, multiple data (SIMD) accelerate strategies like MMX, SSE, AVX (x86) or NEON (arm). I will following the step of GameEngineFromScratch and try to use ISPC (x86) from Intel.