You probably have heard about short vector types in C++ AMP from the introduction of the concurrency::graphics namespace, and I will also assume you have read the post on the norm and unorm scalar types.

In this post, I’m going to provide more details on short vector types, which emulate the behavior of short numerical vectors which are available in shader languages like HLSL and widely used in computer graphics programming. First I’ll talk about the concept, then about construction, then about the swizzling accessors, then about other operations, and I will end with an example of using metadata and reflection.

## Short Vector Types Basics

To start using short vector types, you need to do the following:

`#include <amp_graphics.h>`

using namespace concurrency::graphics;

C++ AMP short vector types are named as: *ScalarType_N*, where *ScalarType* is one of *int, uint, float, double, norm, and unorm*, and *N* is one of *2*, *3*, or *4*. Note that *uint* is a typedef of *unsigned int*, available in the concurrency::graphics namespace. A *ScalarType_N* represents a short vector of *N* *ScalarType* components. So in total, there are 18 short vector types. For example, *uint_2* represents a short vector of 2 unsigned integers. Short vector types can be used in both restrict(amp) and restrict(cpu) functions.

For each short vector type, there is a corresponding typedef without the underscore, e.g. *uint2*, which is the syntax typically expected by graphics programmers. These typedef’s reside in the *concurrency::graphics::direct3d* namespace.

## Creating Short Vector Types

There are multiple ways to construct a short vector type object. With default construction, all the components are initialized to 0 values. For example,

uint_2 a;

Both components of “a” are initialized to 0. The copy construction and assignment are component-wise copy. For example,

`uint_2 b1(a); // copy construction`

uint_2 b2;

`b2 = b1; // assignment`

You can also construct a *ScalarType_N* object by specifying the value for each of the *N* components via a constructor that takes *N* *ScalarType* parameters. For example,

float_3 c(1.1f, 2.2f, 3.3f);

You can also initialize all components with the same value. For example,

double_4 d1(3.14);

All four components of “d1” will be initialized to the double value of 3.14. This constructor also allows the implicit conversion from a *ScalarType* to *ScalarType_N*. The scalar value is copied into each component of the short vector object. For example,

`double_4 d2 = 3.14; // double "3.14", is implicitly converted to a double_4`

` // d2: (3.14, 3.14, 3.14, 3.14)`

One can construct a *ScalarType_N* object with an *AnotherScalarType_N* object. Each scalar component of the latter is converted to *ScalarType* using C++ conversion rule for primitive types or the conversions defined for norm/unorm type. * *The constructor is annotated with “*explicit*”, so the conversion from a *ScalarType2_N* to a *ScalarType1_N* has to be explicit. For example,

float_3 e1(1.1f, 2.2f, 3.3f);

`uint_3 e2(e1); // OK, set e2 to ((uint)1.1f, (uint)2.2f, (uint)3.3f) -> (1, 2, 3)`

`uint_3 e3 = e1; // Error, no implicit conversion`

uint_3 e4 = static_cast<uint_3>(e1); // OK, explicit conversion

## Swizzling Accessors

Unlike index, and extent, short vector types have no notion of significance, and cannot be indexed using subscript operator. The only ways to get or set the components of a short vector are

- construction (only set) as explained in previous section, and
- using a swizzling format accessor.

C++ AMP short vector types offer the syntax for accessing the component(s) as

ScalarType_N_Obj.ident

*ScalarType_N_Obj* is a short vector of type *ScalarType_N*. The* ident *is an identifier specifying which components out of the *ScalarType_N_Obj* are selected for accessing. The above expression can be an l-value or an r-value. The *ident* is a combination of no more than* N* characters. The individual character in *ident* may be *x, y, z, w, r, g, b, *and* a*. Note that *x, y, z, and w* means the first, the second, the third, and the fourth component of the short vector respectively. As you may have guessed, *r*, *g*, *b*, *a* are synonymous for *x*, *y*, *z*, *w*. In *ident*, characters from group *x*, *y*, *z*, *w*, cannot be combined with characters from group *r*, *g*, *b*, *a*. Single component accessor, such as *x* or *y,* is type of *ScalarType*. Multi-component accessor is type of *ScalarType_M*, where *M* is the number of components in *ident*. For example, the type of *e3.xy* is *uint_2*.

The *ident *can use swizzling format to rearrange the components from *ScalarType_N_Obj*. For example, one can write,

double_4 f1(3.14);

`double_4 f2 = f1.xyzw; // no rearrangement`

`double_4 f3 = f1.zwxy; // rearrangement`

`f3.zxwy = f1.xwyz; // rearrangement`

The above code is equivalent to

double_4 f2 = f1;

double_4 f3(f1.z, f1.w, f1.x, f1.y);

f3.z = f1.x; f3.x = f1.w; f3.w = f1.y; f3.y = f1.z;

For the initial release, C++ AMP does not offer all the possible swizzling formats. For a *ScalarType_N*, the swizzling format can have 1 to *N* components, each from the character group *x*, *y*, *z*, and *w*, and in any order. Repetition of a character is not allowed. The same set of swizzling formats are available using the character group *r*, *g*, *b*, and *a*. Below are examples of supported usages:

double_4 g1(3.14);

`double g2 = g1.y;`

double_2 g3 = g1.wx;

double_3 g4 = g1.ywx;

g4.rbg = g1.xwy;

g4.r = g1.z;

g4.xy = g1.ba;

Here are some unsupported usages:

`double_2 h1 = g1.xx; // repetition of x`

`double_3 h2 = g1.xyr; // r could not be combined with xy`

`h1.xyz = g1.xy; // h1 can only have up to 2 components`

For unsupported swizzling format, you will have to explicitly use constructors.

`double_2 i1 = double_2(g1.x, g1.x); // to achieve g1.xx`

With Visual Studio 2012, you can always use Intellisense to discover the available usages of swizzling formats, for example:

## Operations

A variety of operations are supported for short vector types. Binary operators, compound assignment operators, and relational operators, are defined between two short vector types with the same *ScalarType* and *N*. Below are the supported operators:

- Arithmetic operators: +, -, *, /
- Bitwise operators (only for
*int_N*and*uint_N*): %, ^, |, &, <<, >>, and ~ - Compound assignment operators:
- arithmetic operations: +=, -=, *=, /=
- bitwise operations: only for
*int_N*, and*uint_N*: %=, ^=, |=, &=, <<=, >>= - Equality operators: ==, !=
- Increment and decrement operators: ++, — both prefix and postfix
- Unary “-“ operator (Not available for
*unorm_N*, and*uint_N*)

For all above operators, the operation is carried component-wise except for equality/inequality operators. For equality operator, “==”, it returns true if all components are equal. For inequality operator, “!=”, it returns true if any pair of components are not equal. Here are some examples:

`uint_2 j1(1U, 2U), j2(3U, 4U); // j1 : (1, 2); j2 : (3, 4)`

`uint_2 j3 = j1.yx + j2; // j3 : (5, 5)`

`j3 += j2; // j3 : (8, 9)`

`j3--; // j3 : (7, 8)`

`uint_2 j4 = ~j3; // j4 : (fffffff8, fffffff7) `

`int_3 j5(1, 2, 3); // j5 : (1, 2, 3)`

`int_3 j6 = -j5; // j6 : (-1, -2, -3)`

bool j7 = (j5 == j6); // j7 : false

For any operation that is defined between two short vectors, the same operation can be performed between a short vector and a scalar. The scalar’s type should either be the same as the short vector’s *ScalarType*, or be a type that could be implicitly converted to *ScalarType_N* with only one user-defined conversion. The operation is carried component-wise between each component of the short vector and the scalar. Here are some examples:

`uint_2 k1(1U, 2U); // k1 : (1, 2)`

`uint_2 k2 = k1 + 3U; // k2 : (4, 5)`

`uint_2 k3 = 4U + k1; // k3 : (5, 6) `

`k3 += 5U; // k3 : (10, 11)`

`int k4 = 6;`

`k3 += k4; // int -> uint -> uint_2, `

` // with only one user defined conversion uint -> uint_2`

`// k3 : (16, 17)`

bool k5 = (k3 != 1); // k5: true;

## Metadata and Reflection

C++ AMP also provides some C++ metaprogramming facilities for short vector types.

Every short vector type exposes its number of components as a static member called *size* of type *int*, e.g., *double_4 *contains the following:

static const int size = 4;

Every short vector type exposes its scalar type as a typedef, consistent with STL containers, e.g., *double_4* has:

typedef double value_type;

Another generic way to retrieve the *size* and *value_type* of a short vector type is to use a class called *short_vector_traits,* which allows you to write code like:

`short_vector_traits<double_4>::size // return 4`

`short_vector_traits<double_4>::value_type // a typedef resolving to “double”`

*short_vector_traits *also support scalar types, for example:

short_vector_traits<double>::size // return 1

short_vector_traits<double>::value_type // a typedef resolving to “double”

We also provide a class called *short_vector*, which is useful for programming short vectors generically, as

template<typename ScalarType, int N>

`struct short_vector;`

*short_vector *only has a typedef member called *type*. For the six *ScalarType*’s, when N is 1, *short_vector::type* is *ScalarType*; when N is 2, 3, or 4, *short_vector::type* is *ScalarType_N*. For all other instantiation of *short_vector*, a static assertion will be triggered. For example,

short_vector<int, 1>::type a; // int

short_vector<int, 2>::type b; // int_2

short_vector<int, 5>::type c; // static assertion

Following is an example using the above reflection classes. In this release, we don’t provide a stream operator for short vector types. However, you can implement it using *short_vector*, and *short_vector_traits*.

template <typename ScalarType, int N>

`class stream_helper;`

`// specialization for N == 2`

`// ...`

`// specialization for N == 3`

template<typename ScalarType>

`class stream_helper<ScalarType, 3>`

{

`public:`

typedef typename short_vector<ScalarType, 3>::type short_vector_type;

static std::ostream& stream(std::ostream &os, const short_vector_type& svt)

{

os << "(" << svt.x << ", " << svt.y << ", " << svt.z << ")";

` return os;`

}

};

`// specialization for N == 4`

`// ...`

template <typename ShortVectorType>

`inline`

`typename std::enable_if<(short_vector_traits<ShortVectorType>::size > 1), `

std::ostream&>::type

operator<<(std::ostream &os, const ShortVectorType& svt)

{

return stream_helper<typename short_vector_traits<ShortVectorType>::value_type,

short_vector_traits<ShortVectorType>::size>::

stream(os, svt);

}

Then you can write code like,

int_3 a(1, 2, 3);

float_3 b(4.1f, 5.2f, 6.5f);

std::cout << a << std::endl;

std::cout << b << std::endl;

It outputs:

(1, 2, 3)

(4.1, 5.2, 6.5)

This concludes my introduction to short vector types. As always, your feedback is welcome below or in our MSDN Forum.