Small Vectors Proposal

Mikola Lysenko mclysenk at mtu.edu
Tue Jan 30 09:48:26 PST 2007


Coordinate geometry is a fundamental part of mathematics.  Many 
important problems in computer science are easily expressed in terms of 
vectors and products.  There are many domains which could benefit from 
enhanced vector performance, especially in scientific and graphical 
applications.

Despite their importance, very few languages provide support for vector 
types.  The prevailing notion is that it should be up to the compiler to 
optimize and somehow automatically determine where SIMD code can be 
applied.  The problem with this approach is that it is often very 
difficult for the compiler to spot where a SIMD operation can be 
applied.  Code must be carefully tweaked to make the compiler recognize 
the correct possible optimization.  Even then, many programmers still 
write their own low-dimensional vector classes for convenience, and 
perform all SIMD optimizations by hand in assembler.

It is important to make the distinction between low dimension vectors 
and other higher order arrays.  This is crucial since the higher 
dimensional arrays are often more tenuously connected with any sort of 
geometric or physical interpretation.  Moreover, many architectures are 
specially optimized for the lower dimensional cases, and offer special 
registers which are virtually impossible to properly exploit using 
libraries alone.  The situation is analogous to floating point 
arithmetic, proper hardware support requires language level integration. 
  Shader languages (Cg/GLSL) already provide extremely robust vector 
arithmetic support, and give some direction to this proposal.  Here are 
the proposed language extensions:

1. Additional primitive types

All numerical primitive types in the language will be supplemented with 
3 additional vector extensions.  Each vector extension will contain 2, 3 
or 4 components since these are best supported by hardware, and the most 
relevant geometrically.  The initial value for each component in each 
type will be the same as that of its base type.  Each the size of each 
vector component will be equal to the size of its base type times its 
dimension.  Here is a complete list of the new types to be added:

Integer types:
byte2, byte3, byte4, short2, short3, short4, int2, int3, int4, long2, 
long3, long4, cent2, cent3, cent4, ubyte2, ubyte3, ubyte4, ushort2, 
ushort3, ushort4, uint2, uint3, uint4, ulong2, ulong3, ulong4, ucent2, 
ucent3, ucent4

Floating types:
float2, float3, float4, double2, double3, double4, real2, real3, real4

Imaginary types:
ifloat2, ifloat3, ifloat4, idouble2, idouble3, idouble4, ireal2, ireal3, 
ireal4

Complex types:
cfloat2, cfloat3, cfloat4, cdouble2, cdouble3, cdouble4, creal2, creal3, 
creal4

All new vector types are pass-by-value, and may be used in combination 
with any number of other types.  The following expressions would all be 
valid:

int2[] a;
float4* b;
char[][ucent4] c;


2. Vector literals

It must be possible to declare a vector literal with unambiguously as an 
argument to a function, or any number of other situations.  This syntax 
must be concise since it will be used often, and it must be flexible, 
allowing vectors to be constructed via concatenation.  In this light, 
the following is proposed:

float4(1, 2, 3, 4);
int2(100, 200);
creal3(1 + 2i, 3+4i, 5-6i);

Additionally, this syntax will also support the construction of vectors 
from other sub vectors.  This can be useful for augmenting a 3-vector 
with a fourth homogenous component for transformation amongst other 
things.  Here are some examples:

float4(float3(x, y, z), 1);		// returns (x, y, z, 1)
int3(0, int2(a, b));			// returns (0, a, b)
creal4(ireal2(r, z), real2(p, q));	// returns (r, z, p, q)


3. Casting rules

Vector types will obey the same casting rules as their base type within 
a single dimension.  It is not possible to use a cast to change the 
dimension of a vector.

cast(float2)(int2(1, 2));	//ok
cast(int)(float2(3, 4));	//error, int is not the same dimension as float2
real3(a, b) + ireal(c, d);	//ok, type of expression is creal3


4. Component-Wise Arithmetic

All of the arithmetic operators on primitive types will be extended such 
that when applied to vectors they operate on each component 
independently.  The size and types of the vectors must be compatible 
with the operator.

float4(1, 2, 3, 4) + float4(3, 5, 6, 7); 	// Vector-Vector operation, 
returns (4, 7, 9, 11)
float3(1, 5, 6) * float3(1, 0, 2);		// returns (1, 0, 12)
uint2(0xFF00FF, 0x00FF88) & uint2(0xFF, 0xFF);	// returns (0xFF, 0x88)

int3(x, y, z) * int2(a, b);			// error, size of vectors does not match

Additionally, scalar vector operations will be supported.  Each 
scalar-vector operator is applied per-component within the vector.

int2(0, 3) * 5;					// Vector-Scalar operation, returns (0, 15)
byte2(2, 5) + 6;				// returns (8, 11)

Note that the only two comparison operators allowed on vectors are == 
and !=.  The problem with <, >, etc. is that there is no commonly agreed 
upon definition for such things.  Therefore the operators are omitted to 
avoid confusion.

int2(1, 2) == int2(1, 2);		//ok, evaluates to true
float3(1, 0, 0) < float3(0, 0, 1);	// error, < is undefined for vectors


5. Swizzle operations

In many vector applications, it is often necessary to reorder the 
components.  In shader languages this is accomplished via 'swizzle' 
properties in each vector type.  Each swizzle selects a set of 1-4 
vector components and places them into a new vector of the given 
dimension.  Conventionally these components have the following names:

x - first component
y - second component
z - third component
w - fourth component

A swizzle is then given as a property consisting of 1-4 components in a 
given order.  Here are some examples:

float2(a, b).yx;		// evaluates to (b, a)
float3(1, 2, 3).zzyx;		// evaluates to (3, 3, 2, 1)
byte4('a', 'b', 'c', 'd').w;	// evaluates to 'd'
creal3(1 + 2i, 0, 4i).zzzz;	// evaluates to (4i, 4i, 4i, 4i)

int2(1, 2).z;			// error, vector does not have 3 components

These can be useful when projecting homogenous vectors into a lower 
dimension subspace, or when computing various products.  Here is an 
example of a cross product implemented using swizzles:

float3 cross(float3 a, float3 b)
{
	return a.zxy * b.yzx - a.yzx * b.zxy;
}

And here is a simple homogenous projection:

float3 project(float4 hg)
{
	return hg.xyz / hg.w;
}


6. Standard Library Additions

The basic vector type additions have been kept as spartan as possible to 
avoid cluttering the basic language, while still allowing the maximum 
expressiveness.  Instead of defining various vector products as 
properties, they are implemented within a standard library extension. 
The reason for this is simple; many users may wish to redefine these 
products based on their own coordinate system.  Within the standard 
library, all vector products are performed in right-handed euclidean 
space.

Vector specific functions will be placed in a new library std.vmath, and 
all functions in std.math will be extended to work on vectors as well 
primitive types.  Here is a list of functions std.vmath will support 
along with a brief description:

dot(a, b) - Computes dot product of two vectors:
	a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w

cross(a, b) - Computes the cross product as defined above.  Only defined 
for 3d vectors.

perp(a, b) - Computes the perp product of two vectors as (a.x * b.y - 
a.y * b.x).  Only defined for 2d vectors.

mag(a) - Computes the magnitude of a vector, given as:  sqrt(x * x + y * 
y + z * z + w * w);

mag2(a) - Compute the square of the magnitude of a vector.

norm(a) - Returns a / mag(a)

lerp(a, b, t) - Compute an interpolated position between two vectors.  a 
* (1 - t) + b * t

Additionally, it would be convenient to have quaternion functions 
supported in the standard library.  A quaternion q would be a 4 vector 
with the following form:  q = q.w + q.x * i + q.y * j + q.z * k

qconj(a) - Computes the quaternion conjugate of a.  Given as 
float4(-a.xyz, a.w)

qmul(a, b) - Computes the quaternion product of two 4d vectors.

qrot(a, b) - Performs a quaternion rotation/dilation on the 3d vector b 
by the 4d quaternion a.


Finally, there are range manipulation functions:

clamp(a, min, max) - Clamps each component of a to the range [min, max]

minv(a) - Returns the smallest component in the vector a

maxv(a) - Returns the largest component in the vector a




More information about the Digitalmars-d mailing list