How gdext Implements GLSLang-Style Vector Swizzling in Nim

gdext implements GLSLang-style vector swizzling through a compile-time macro in src/gdext/swizzles.nim that maps component characters to array indices, validates patterns at compile time, and dispatches to zero-copy views for continuous slices or element-wise indexing for non-continuous patterns.

The gdext-nim library provides Nim bindings for the Godot 4.x engine, enabling developers to write game logic with GPU-shader ergonomics on the CPU. Its vector swizzling implementation mirrors GLSLang's xyzw component selection syntax while maintaining Nim's static type safety and zero-cost abstractions through compile-time code generation.

The swizzle Macro Architecture

The core implementation resides in src/gdext/swizzles.nim, where a macro processes swizzle tokens entirely at compile time to generate optimal array access code.

Component Mapping via keyMap

The macro translates swizzle characters to array indices through the keyMap procedure. This mapping follows the standard GLSLang convention where x → 0, y → 1, z → 2, and w → 3.

proc keyMap(key: char): int

When processing a swizzle token like xyzw, the macro iterates character-by-character through the string literal, calling keyMap to resolve each component to its corresponding integer index. These indices accumulate in a compile-time sequence (seq[int]) that drives subsequent code generation.

Compile-Time Validation

Before emitting any code, the macro performs strict validation. It checks that every character in the token is a valid component and that each resolved index does not exceed the source array's length (I). If the token contains invalid characters (such as a or q) or attempts to access components beyond the vector's dimension (like requesting w from a 3-element array), the macro emits a compile-time error immediately.

if i == -1: error("Invalid component")
if i >= I: error("Index out of bounds")

This guarantees that only valid, in-bounds swizzle patterns reach the code generation phase, eliminating runtime bounds checking overhead.

Dispatch Strategy Based on Component Count

The macro branches based on the number of indices collected:

  • 0 components – Generates no output
  • 1 component – Emits a simple bracket expression v[indices[0]]
  • 2+ components – Analyzes index continuity to select between view-based or copy-based strategies

This dispatch logic resides in a case indices.len statement that determines the most efficient code generation path for the specific pattern requested.

Memory-Efficient Slice Handling

For multi-component swizzles, gdext distinguishes between continuous and non-continuous index patterns to optimize memory access patterns.

Zero-Copy Continuous Slices with subVec

When indices form a continuous range (e.g., xy maps to [0, 1] or yz maps to [1, 2]), the macro invokes subVec, a compile-time procedure that returns a sub-array view without copying data.

proc subVec[N; T](v: var array[N,T]; offset, length: static[int]): var array[length,T] =
  cast[ptr array[length, T]](addr v[offset])[]

This implementation casts a pointer to the original array segment at the specified offset, yielding a view of type array[length, T]. Because offset and length are static[int] parameters, the compiler resolves the view's type at compile time, resulting in zero runtime overhead for continuous swizzles like v.*xy or v.*yz.

Non-Continuous Swizzle Patterns

For non-continuous patterns such as xz (indices [0, 2]) or wzyx (indices [3, 2, 1, 0]), the macro generates code that constructs a new array by individually indexing the source vector. The generated code creates a temporary binding (let nv = v) to prevent multiple evaluations, then builds the result array through element-wise access:

let nv = v
[nv[indices[0]], nv[indices[1]], ...]

While this approach involves creating a new array instance, it still operates at compile-time determined indices without runtime loops or dynamic allocation.

Developer Syntax and Usage

The library exposes swizzling through an intuitive operator syntax that mirrors GLSLang conventions.

The .* Template Interface

A thin template definition provides the dot-swizzle syntax familiar to shader programmers:

template `.*`*[N; T](v: array[N, T]; key: untyped): untyped =
  swizzle(v, key)

This template intercepts expressions like v.*xyz and forwards them to the swizzle macro, enabling natural vector component selection within Nim code.

Practical Code Examples

The following examples demonstrate swizzling on static arrays equivalent to GLSL vector types:


# Define a 4-component vector type

type Vec4 = array[4, float]

let v: Vec4 = [1.0, 2.0, 3.0, 4.0]

# Continuous swizzle returns a zero-copy view

let xy = v.*xy          # array[2, float] = [1.0, 2.0]

# Single component extraction

let z = v.*z            # float = 3.0

# Non-continuous swizzle creates new array

let xwz = v.*xwz        # array[3, float] = [1.0, 4.0, 3.0]

# Direct macro invocation (equivalent)

let yz = swizzle(v, yz)

All operations compile to direct array indexing or pointer casts, with continuous patterns requiring no additional memory allocation.

Performance and Type Safety

Because the swizzle macro operates entirely at compile time, the resulting code exhibits performance characteristics identical to hand-written array access. Continuous swizzles leverage subVec to provide slice views without copying, while non-continuous patterns generate unrolled indexing code. The implementation restricts operations to static Nim arrays (array[I, T]), ensuring that vector dimensions and component types are known at compile time and preserving Nim's static type checking guarantees.

Summary

  • gdext implements GLSLang-style vector swizzling in src/gdext/swizzles.nim using a compile-time macro system.
  • The keyMap procedure translates xyzw characters to array indices 0..3 with full compile-time validation.
  • Continuous swizzles (e.g., xy, yz) use subVec to return zero-copy pointer-cast views of the original array.
  • Non-continuous swizzles (e.g., xz, wzyx) generate new arrays through compile-time unrolled element indexing.
  • The .* template provides intuitive GLSLang syntax (v.*xyz) while maintaining Nim's static type safety.
  • All operations resolve at compile time, producing zero runtime overhead for valid swizzle patterns.

Frequently Asked Questions

What file contains the vector swizzling implementation in gdext-nim?

The implementation resides in src/gdext/swizzles.nim. This file contains the swizzle macro, the subVec helper procedure for zero-copy slices, and the .* template that provides the user-facing syntax.

Does gdext vector swizzling work at runtime or compile time?

The swizzling operates entirely at compile time. The macro parses the swizzle token, validates component indices, and generates specific array access code or pointer casts before compilation completes. This eliminates runtime parsing overhead and ensures invalid patterns fail during compilation rather than at runtime.

Can I use swizzling on dynamic arrays or sequences in Nim?

No. The current implementation specifically requires static arrays (array[I, T]) where the length I is known at compile time. This constraint enables the macro to validate that swizzle components (like w requiring index 3) do not exceed the array bounds and allows subVec to create properly typed slice views with static length parameters.

What is the performance difference between continuous and non-continuous swizzles?

Continuous swizzles (sequential indices like xy or rgb) incur zero overhead beyond a single pointer cast through the subVec helper, returning a view into the existing memory without copying elements. Non-continuous swizzles (non-sequential indices like xz) generate code to construct a new array instance by copying selected elements, which involves minimal overhead proportional to the number of components selected but still avoids loops or dynamic allocation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →