PackedVector Types
All 17 XNA 4.0 PackedVector types are fully implemented with 100% unit test coverage. Every type correctly handles edge cases including overflow, underflow, NaN, and signed/unsigned normalization boundary values.
Overview
The Microsoft::Xna::Framework::Graphics::PackedVector namespace contains types that pack floating-point component values into compact integer representations. These types are used to describe vertex buffer layouts and texture formats, reducing memory bandwidth on the GPU.
Each packed type stores its data in the smallest integer that can hold all components. The GPU can decode them natively in hardware, so pack-on-CPU, unpack-in-shader is both fast and lossless within the precision of each format. Using packed types instead of raw float arrays can cut vertex buffer size by 50–75% for typical 3D meshes.
All 17 types are available in the Microsoft::Xna::Framework::Graphics::PackedVector namespace in CNA, matching the XNA 4.0 public API exactly. Two additional names, Vector2 and Vector4, appear in the XNA 4.0 PackedVector namespace as type aliases; in CNA these resolve to Microsoft::Xna::Framework::Vector2 and Microsoft::Xna::Framework::Vector4 respectively.
IPackedVector interface
Every packed type implements the IPackedVector<T> interface, where T is the underlying integer storage type (uint8_t, uint16_t, uint32_t, or uint64_t). The interface defines three members:
| Member | Description |
|---|---|
T PackedValue |
Gets or sets the raw packed integer. Reading this gives the bit-exact stored value; writing it replaces all components simultaneously. |
void Pack(value) |
Converts a floating-point vector (or scalar) into the packed representation and stores the result in PackedValue. Component values outside the representable range are clamped. |
Vector4 ToVector4() |
Unpacks the stored value and returns it as a Vector4. Components not present in the format are filled in as 0 (X, Y, Z) or 1 (W) to match XNA 4.0 behaviour. |
Types with a single component also implement the non-generic IPackedVector base interface, which exposes only ToVector4(). This allows generic rendering code to unpack any packed type to Vector4 without knowing the concrete type.
Complete type reference
| Type | Storage | Components | Range | Notes |
|---|---|---|---|---|
Alpha8 |
uint8 |
1 × unorm8 | 0..1 | Single alpha channel; maps 0–255 to 0.0–1.0 |
Bgr565 |
uint16 |
B5G6R5 | 0..1 | Blue 5 bits, Green 6 bits, Red 5 bits; compact opaque colour |
Bgra4444 |
uint16 |
B4G4R4A4 | 0..1 | 4 bits per channel; very compact RGBA for low-precision textures |
Bgra5551 |
uint16 |
B5G5R5A1 | 0..1 | 1-bit alpha (fully opaque or fully transparent) |
Byte4 |
uint32 |
4 × uint8 | 0–255 | RGBA byte quad; raw unsigned integers, not normalized |
HalfSingle |
uint16 |
1 × float16 | ±65504 | IEEE 754 half-float; see Half-float section below |
HalfVector2 |
uint32 |
2 × float16 | ±65504 | X and Y components packed as two consecutive float16 values |
HalfVector4 |
uint64 |
4 × float16 | ±65504 | X, Y, Z, W packed as four consecutive float16 values |
NormalizedByte2 |
uint16 |
2 × snorm8 | -1..1 | Signed normalized; -128 maps to -1.0, 127 maps to +1.0 |
NormalizedByte4 |
uint32 |
4 × snorm8 | -1..1 | Common for vertex normals and tangents in compact vertex streams |
NormalizedShort2 |
uint32 |
2 × snorm16 | -1..1 | Higher precision than NormalizedByte2; -32768 maps to -1.0 |
NormalizedShort4 |
uint64 |
4 × snorm16 | -1..1 | Four signed normalized 16-bit components |
Rg32 |
uint32 |
2 × unorm16 | 0..1 | Two unsigned normalized 16-bit components; 0–65535 maps to 0.0–1.0 |
Rgba1010102 |
uint32 |
R10G10B10A2 | 0..1 | 10 bits per RGB channel, 2-bit alpha; HDR-friendly wide-gamut format |
Rgba64 |
uint64 |
4 × unorm16 | 0..1 | Wide-format RGBA; each channel maps 0–65535 to 0.0–1.0 |
Short2 |
uint32 |
2 × int16 | -32768..32767 | Raw signed 16-bit integers; not normalized |
Short4 |
uint64 |
4 × int16 | -32768..32767 | Four raw signed 16-bit integers; common for bone indices in skinning |
IEEE 754 half-float (float16)
The three half-float types — HalfSingle, HalfVector2, and HalfVector4 — use the 16-bit IEEE 754 binary16 format, commonly called half-float or fp16. CNA implements the full conversion path in C++23 without relying on compiler extensions or hardware intrinsics, so the code is portable across all supported platforms.
Format layout
| Field | Bits | Position | Description |
|---|---|---|---|
| Sign | 1 | 15 | 0 = positive, 1 = negative |
| Exponent | 5 | 14–10 | Biased by 15; exponent 0 = subnormal, 31 = infinity/NaN |
| Mantissa | 10 | 9–0 | Fractional bits; implicit leading 1 for normal numbers |
The representable normal range is approximately ±6.10×10-5 to ±65504. The smallest positive subnormal is approximately 5.96×10-8.
HalfTypeHelper
All float32 ↔ float16 conversions go through the internal HalfTypeHelper class. It handles every case defined by the IEEE 754-2008 standard:
- Normal numbers — re-biased exponent and rounded mantissa (round-to-nearest-even).
- Subnormals — exponent field is 0, mantissa is non-zero; correctly decoded during unpack.
- Positive and negative zero — sign bit preserved; both ±0 round-trip correctly.
- Positive and negative infinity — exponent field all-ones, mantissa zero; preserved on pack and unpack.
- NaN — exponent field all-ones, mantissa non-zero; both quiet and signaling NaN are packed as quiet NaN (all mantissa bits set to 1) to avoid platform-dependent signaling behaviour.
- Overflow — float32 values with magnitude greater than 65504 saturate to ±infinity in the float16 result.
- Underflow — float32 values smaller than the smallest subnormal flush to ±zero.
| Method | Signature | Description |
|---|---|---|
Pack |
uint16_t Pack(float value) |
Converts a 32-bit float to a 16-bit half-float bit pattern. |
Unpack |
float Unpack(uint16_t value) |
Converts a 16-bit half-float bit pattern back to a 32-bit float. |
Code examples
Creating and packing a HalfVector4
// Pack four floats into 64 bits (4 x float16)
HalfVector4 hv4;
hv4.Pack(Vector4(1.0f, 0.5f, -0.25f, 2.0f));
// Access the raw bit pattern
uint64_t bits = hv4.PackedValue;
// Unpack back to float
Vector4 result = hv4.ToVector4();
// result.X == 1.0f, result.Y == 0.5f, result.Z == -0.25f, result.W == 2.0f
Using NormalizedByte4 for a vertex normal
NormalizedByte4 is a compact format for storing unit normals and tangents in vertex buffers. Each component uses a signed 8-bit integer mapped to the -1..1 range, cutting normal storage from 12 bytes to 4 bytes per vertex.
// Pack a vertex normal (unit vector) into 4 bytes
NormalizedByte4 packedNormal;
packedNormal.Pack(Vector4(0.0f, 1.0f, 0.0f, 0.0f)); // pointing up
// Use as part of a vertex declaration
VertexElement normalElem(
offsetof(MyVertex, Normal),
VertexElementFormat::Byte4,
VertexElementUsage::Normal,
0
);
// Unpack on retrieval
Vector4 normal = packedNormal.ToVector4();
// normal.X == 0.0f, normal.Y == 1.0f, normal.Z == 0.0f
Using Rgba1010102 for HDR colour data
Rgba1010102 packs red, green, and blue each into 10 bits (0..1023 per channel, normalized to 0.0..1.0) and alpha into 2 bits. This provides roughly 3× the per-channel precision of Bgra5551 in the same 32-bit footprint, making it suitable for HDR render targets and wide-gamut textures.
// Pack an HDR colour (values above 1.0 are clamped by the packer)
Rgba1010102 hdrColor;
hdrColor.Pack(Vector4(0.95f, 0.72f, 0.12f, 1.0f));
// The 2-bit alpha maps 0.0..1.0 to 0..3
// The 10-bit RGB channels give ~0.001 precision per channel
Vector4 unpacked = hdrColor.ToVector4();
Checking HalfSingle precision and special values
// Normal value round-trip
HalfSingle hs;
hs.Pack(1.5f);
float v = hs.ToVector4().X; // == 1.5f exactly (representable in float16)
// Infinity is preserved
hs.Pack(std::numeric_limits<float>::infinity());
// hs.PackedValue == 0x7C00 (positive infinity in float16)
// Values beyond float16 range saturate to infinity
hs.Pack(70000.0f);
// hs.PackedValue == 0x7C00 (overflow -> +inf)
// NaN is preserved as quiet NaN
hs.Pack(std::numeric_limits<float>::quiet_NaN());
// hs.PackedValue has exponent=0x1F and non-zero mantissa
// Zero sign is preserved
hs.Pack(-0.0f);
// hs.PackedValue == 0x8000 (negative zero in float16)