🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

C++ swizzle selector like GLSL?

Started by
19 comments, last by Juliean 2 years, 6 months ago

Is there a way to make a swizzle selector for vector classes in C++ that works like they do in GLSL? Something like this, where xyz is a pointer to the x member, and tricks the compiler into thinking it is a Vec3 object:
struct vec3
{
float x, y, z;
};

struct vec4
{
float x, y, z, w;
Vec3& xyz;
};

10x Faster Performance for VR: www.ultraengine.com

Advertisement

My C++ is pretty rusty by now, but I think that you should be able to simply create methods with the relevant names--i.e. “vec4::xyz()”--and have them return appropriate values. Something like this (again, with likely-rusty C++, so please forgive minor errors):

class vec4
{
	x, y, z;
	
	vec3 xyz()
	{
		return vec3(x, y, z);
	}
};

This is, however, of course an exhaustive approach--it involves manually creating all desired permutations.

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

That's returning a new value. I want a pointer that can be modified, so you can do things like this:

color.rgb *= 2.0f

10x Faster Performance for VR: www.ultraengine.com

Ah, I see.

Hum, that is more difficult! There may be a more-elegant way of doing it, but I could see some trickery involving an intermediate class that holds a pointer to the originating vector and that overloads various operators, perhaps…

However, at that point the rustiness of my C++ becomes an impediment, I fear, so for the moment I'll leave this to others!

MWAHAHAHAHAHAHA!!!

My Twitter Account: @EbornIan

You could look at glm, which supports swizzling.
(Not sure if my link points to the actual implementation - too cryptic fro me to read)

https://github.com/g-truc/glm/blob/master/glm/detail/_swizzle.hpp

Thaumaturge said:
class vec4 
{ 
	float x, y, z, w; 
	vec3& xyz() 
	{ 
		return (vec3&)*this; 
	} 
};

Changed to returning reference, so could change the data as with a pointer. I do such casting in place if i need it. Real swizzling seems too much pain to be worth it.

You could achieve real swizzling with a structure storing a & for each element instead of a value:

struct vec3_wrapper
{
	void operator*=(float value)
	{
		x *= value;
		y *= value;
		z *= value;
	}
	
	int& x;
	int& y;
	int& z;
};

struct vec3
{
	vec3_wrapper xxx(void) &
	{
		return {x, x, x};
	};
	
	vec3 xxx(void) const
	{
		return {x, x, x};
	};
};

The main problem here is that this vec3_wrapper doesn't work on const/temporay variables, so you need potentially a few different overloads for each operation (I'm not sure if my two overloads above are enough to handle all cases). Definately doable, but a lot of work.

Swizzling from xyzw to xyz is trivial: just use inheritance.

struct vec3 {
  float x, y, z;
};

struct vec4 : private vec3 {
  using vec3::x;
  using vec3::y;
  using vec3::z;
  float w;
  vec3& xyz() { return *this; }
};

True swizzling requires wrapper types, which is kind of a pain.

struct vec3 {
  float values[3];
  float &operator[](std::size_t i) { return this->values[i]; }
  float &x() { return this->values[0]; }
  float &y() { return this->values[1]; }
  float &z() { return this->values[2]; }
};

template<std::size_t x_index, std::size_t y_index, std::size_t z_index, typename base_type> struct swizzled_vec3 {
  base_type &base;
  swizzled_vec3(base_type& base) : base(base) {}
  float &x() { return this->base[x_index]; }
  float &y() { return this->base[y_index]; }
  float &z() { return this->base[z_index]; }
  float &operator[](std::size_t i) {
    switch (i) {
    case 0: return this->x();
    case 1: return this->y();
    case 2: return this->z();
    }
  }
};

template<typename base_type> swizzled_vec3<1, 0, 2, base_type> yxz(base_type &org) {
  return swizzled_vec3<1, 0, 2, base_type>(org);
}

void test() {
  vec3 v;
  v.x() = 5;
  v.y() = 7;
  v.z() = 13;
  // Swizzled vector components can be read.
  assert(yxz(v).x() == 7);
  // Swizzled vectors can be swizzled again.
  assert(yxz(yxz(v)).x() == 5);
  // Swizzled vector components can be written.
  yxz(v).y() = 17;
  assert(v.x() == 17);
}

You could achieve real swizzling with a structure storing a & for each element instead of a value:

The cost of this is massive, and any math library that tries to treat your vector as, you know, just a few contiguous numbers, will fail.

It is the case that computers are so fast, that for any “small” project, any approach will “work.” The performance cost only shows up once the project scales up. Unfortunately, games very quickly scale up, because there are tens of thousands of triangles in each character, thousands of physics objects in an explosion, thousands of packets per second in a multiplayer server, and so on.

If you want swizzling, in C++, there's unfortunately no way to do that without having to add the () function call decorator:

struct Vector4 {
    float x, y, z, w;
    
    inline Vector4 xxxx() const { return Vector4{ x, x, x, x }; }
    ...
    inline Vector4 yzxw() const { return Vector4{ y, z, x, w }; }
    ...
};

Vector4 foo{ 1, 2, 3 4 };

Vector4 swiz = foo.yzxw();

You'll most likely want to write a simple program/script that generates the 256 different swizzle functions for your, and just paste that code into the definition of your vector type. (only 27 functions if it's Vector3.) Have to do it once, and then it's done.

enum Bool { True, False, FileNotFound };

hplus0603 said:

You could achieve real swizzling with a structure storing a & for each element instead of a value:

The cost of this is massive, and any math library that tries to treat your vector as, you know, just a few contiguous numbers, will fail.

Only if the optimizer doesn't optimize the entire wrapper struct away. Which, to be fair, it might not - modern optimizers are pretty decent but not infallible.

This topic is closed to new replies.

Advertisement