Swizzling vectors in C

2026/07/27

#c, #experiment

Swizzling vectors in C

In GLSL it’s possible to access arbitrary vector accessors vec.xyz, vec.yw. I always thought this was neat, so I thought about trying to implement it in C.

First approach

For the first attempt I used a Ruby script to generate vector unions with each permutation of the vector type’s accessors.

#!/usr/bin/env ruby

$INC = 0

types = [:float, :int, :double]

origElements = [
    [
        [:x, :y],
        [:u, :v],
        [:s, :t]
    ],
    [
        [:x, :y, :z],
        [:r, :g, :b],
        [:s, :t, :p]
    ],
    [
        [:x, :y, :z, :w],
        [:r, :g, :b, :a],
        [:s, :t, :p, :q]
    ]
]

def swizzlePermutations3(e)
    [
        e,
        [[e[0], e[1]], nil],
        [nil, [e[1], e[2]]]
    ]
end

def swizzlePermutations4(e)
    [
        e,
        [[e[0], e[1]], [e[2], e[3]]],
        [[e[0], e[1], e[2]], nil],
        [nil, [e[1], e[2], e[3]]]
    ]
end

def swizzlePermutations(e)
    case e.length
    when 2
        e
    when 3
        swizzlePermutations3 e
    when 4
        swizzlePermutations4 e
    end
end

elements = origElements.map { |e| e.map { |ee| swizzlePermutations ee } }

def swizzlePrintBasic(e, t)
    puts "\tstruct {"
    e.each do |ee|
        puts "\t\t#{t} #{ee};"
    end
    puts "\t};"
    puts "\t#{t} #{e.join}[#{e.length}];"
end

def swizzlePrint(e, t)
    case e[0].class.to_s
    when 'Symbol'
        swizzlePrintBasic e, t
    when 'Array'
        e.each do |ee|
            u = ee.map { |eee| eee.class }.uniq
            if u.length == 1 && u[0] == Symbol
                swizzlePrintBasic ee, t
            else
                eee = ee.map do |eee|
                    case eee.class.to_s
                    when 'Array'
                        eee.join.to_s
                    when 'NilClass'
                        $INC += 1
                        '_' * $INC
                    end
                end
                puts "\tstruct {"
                eee.each do |eeee|
                    if eeee.include? '_'
                        puts "\t\t#{t} #{eeee};"
                    else
                        puts "\t\tvec#{eeee.length}#{t.to_s[0]} #{eeee};"
                    end
                end
                puts "\t};"
            end
        end
    end
end

types.each do |t|
    elements.each_with_index do |e, i|
        puts "typedef union {"
        e.each { |ee| swizzlePrint ee, t }
        puts "\t#{t} elements[#{e[0].length}];"
        puts "\vec#{i+2}#{t.to_s[0]} vec;" if t == :float
        puts "} vec#{i+2}#{t.to_s[0]};"
        puts
        $INC = 0
    end
end

sizes = [2, 3, 4]

types.each do |t|
    t_char = t.to_s[0]
    t_name = t.to_s

    sizes.each do |n|
        puts "typedef union {"

        # Raw elements
        puts "\t#{t_name} elements[#{n*n}];"

        # Column vectors (Column-Major Order)
        # Access as m.col[i]
        puts "\tvec#{n}#{t_char} col[#{n}];"

        # Named columns
        # Access as m.col0, m.col1, ...
        puts "\tstruct {"
        n.times do |i|
            puts "\t\tvec#{n}#{t_char} col#{i};"
        end
        puts "\t};"

        # Scalar members (Column-Major storage: m00, m10, m20... then m01, m11...)
        # Access as m.m00, m.m10, ...
        puts "\tstruct {"
        n.times do |j| # Col index
            n.times do |i| # Row index
                puts "\t\t#{t_name} m#{i}#{j};"
            end
        end
        puts "\t};"

        puts "} mat#{n}#{t_char};"
        puts
    end
end

This generates (for 2, 3, and 4 length vectors):

typedef union {
        struct {
                float x;
                float y;
                float z;
        };
        float xyz[3];
        struct {
                vec2f xy;
                float _;
        };
        struct {
                float __;
                vec2f yz;
        };
        struct {
                float r;
                float g;
                float b;
        };
        float rgb[3];
        struct {
                vec2f rg;
                float ___;
        };
        struct {
                float ____;
                vec2f gb;
        };
        struct {
                float s;
                float t;
                float p;
        };
        float stp[3];
        struct {
                vec2f st;
                float _____;
        };
        struct {
                float ______;
                vec2f tp;
        };
        float elements[3];
} vec3f;

This allows you to access .xyz, .xy, .yz, and others:

vec4f a = (vec4f){1.f, 2.f, 3.f, 4.f);
vec2f b = a.xy;

There is no overhead, a regular struct vec3 { float x, y, z; }; and the vec3f union above are both 12 bytes. The only downside is it’s not flexible. GLSL allows unordered or duplicate accessors, like .xxy.

Second approch

Next, I used map-macro to create a SWIZZLE macro. This allows for arbitrary accessors, but it’s a macro so we lose the vec.x syntax.

#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL(...)  EVAL4(EVAL4(EVAL4(__VA_ARGS__)))

#define MAP_END(...)
#define MAP_OUT
#define MAP_COMMA ,

#define MAP_GET_END2() 0, MAP_END
#define MAP_GET_END1(...) MAP_GET_END2
#define MAP_GET_END(...) MAP_GET_END1
#define MAP_NEXT0(test, next, ...) next MAP_OUT
#define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0)
#define MAP_NEXT(test, next)  MAP_NEXT1(MAP_GET_END test, next)

#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)

#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0)
#define MAP_LIST_NEXT(test, next)  MAP_LIST_NEXT1(MAP_GET_END test, next)

#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__)
#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__)

#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))

#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))

#define MAP_LIST0_UD(f, userdata, x, peek, ...) f(x, userdata) MAP_LIST_NEXT(peek, MAP_LIST1_UD)(f, userdata, peek, __VA_ARGS__)
#define MAP_LIST1_UD(f, userdata, x, peek, ...) f(x, userdata) MAP_LIST_NEXT(peek, MAP_LIST0_UD)(f, userdata, peek, __VA_ARGS__)

#define MAP_LIST_UD(f, userdata, ...) EVAL(MAP_LIST1_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))

#define SWZL_MAP(V, VEC) VEC.V
#define SWIZZLE(V, ...) { MAP_LIST_UD(SWZL_MAP, V, __VA_ARGS__) }

The SWIZZLE macro will map out the arguments out into a new vector.

vec4f a = (vec4f){1.f, 2.f, 3.f, 4.f);
vecNf b = SWIZZLE(a, w, z, y, x);
// SWIZZLE(VEC, ARGS...)
// SWIZZLE(a, w, z, y, x) -> { a.w, a.z, a.y, a.x }
ASSERT(b[0] == 4.f && b[1] == 3.f && b[2] == 2.f && b[3] == 1.f);
// [morgan freeman true.png]

I thought this was a good use of anonymous structs and unions. Please give a star to swansontec for map-macro, it’s a very interesting and surprisingly useful repo. Here is a gist for the files.