NV_half_float

Anyone knows which C++ datatype should be used for the “half” GL type?

Regards,
-Lev

[This message has been edited by Lev (edited 02-24-2003).]

Since half is a 16-bit IEEE-like floating point type, it’ll nicely fit into a float on most machine architectures where float is typically a 32-bit IEEE floating point type.

Well, I can’t see how a 16 bit float would fit into 32 bit one. They have different mantissa sizes. On the other hand 32 bit is te smallest float type available on x86. should I just typedef GLhalf as float? then what’s the point in introducing new entry points if they are the same as existing on existing architectures?

regards,
-Lev

There is no 16 float support on most architextures. YOu have to do a conversion in software I guess.

Copy the mantissa, then OR the exponent and finally OR the sign bit on it. I’m going to check this out myself.

Yep u should adjust it for your needs just match what is written into the specs… But should look as follow…

typedef short VR_FLOAT16;

__forceinline VR_FLOAT16 floatToFloat16(VR_FLOAT f)
{
VR_LONG val;
VR_FLOAT16 res;

__asm{
Fld dword ptr f
Fstp dword ptr val
}

res = (VR_FLOAT16) (val>>MYMANT);
return res;

}
__forceinline VR_FLOAT float16ToFloat(VR_FLOAT16 f16)
{
VR_LONG val;
VR_FLOAT res;

val = (VR_LONG) f16<<MYMANT;
__asm{
Fld dword ptr val
Fstp dword ptr res
}

return res;

}

hth

Hi,

you can also use the portable code provided with the OpenEXR image lib (http://www.openexr.org/) that uses Half floats.

In case you use VC, there is no official VC port yet but it’s along the way - take a look at the OpenEXR forum.

Regards,
Nicolas.

The conversion is not as simple as has been suggested here – it’s fairly messy.

Isn’t the answer “GLhalf”? Or, if you want to be fancy, a C++ struct that contains a GLhalf data element and has a bunch of operator overloads?

  • Matt

There is no native support for half-precision floats in any CPU I’m aware off.

We typedef GLhalf to “unsigned short” on Win32 platforms. To do anything CPU-wise with half-precision floats, you probably want to first convert them to float using a little bit of bit magic along the lines as those described in the posts above. Basically, we chose “unsigned short” because it takes the same number of bits and is the data type you’d want to use in this conversion.

Even with the CPU overhead, GLhalf data is useful for cases where (1) the data sets are pre-converted or (2) the data is both generated and consumed by GPUs that have native support. 16-bit components take 1/2 the bandwidth of 32-bit ones, and that bandwidth (and space) savings can be important if it doesn’t get eaten up by the CPU overhead.

We used half float vertex attributes in most of the GeForce FX launch demos, primarily for basis vectors (tangents, binormals, and normals). Note that for performance reasons, any half array should be padded to be a half2 array (or just left as a float array), and any half3 array should be padded to be a half4 array.

I’ve attached the basic conversion routines we used. We wrapped these in a class to automate the conversion process (like OpenEXR). The code isn’t as efficient as it could be (half->float promotion could be performed with a 64K float lookup table), but it should be relatively self-explanatory. I’m hoping the formatting comes through ok, since I’ve copied it from the original file.

union nv_half_data {
unsigned short bits;
struct {
unsigned long m : 10;
unsigned long e : 5;
unsigned long s : 1;
} ieee;
};

union ieee_single {
float f;
struct {
unsigned long m : 23;
unsigned long e : 8;
unsigned long s : 1;
} ieee;
};

inline static float htof(unsigned short val) {
nv_half_data h;
h.bits = val;
ieee_single sng;
sng.ieee.s = h.ieee.s;

//  handle special cases
if ( (h.ieee.e==0) && (h.ieee.m==0) ) {  // zero
  sng.ieee.m=0;
  sng.ieee.e=0;
}
else if ( (h.ieee.e==0) && (h.ieee.m!=0) ) {  // denorm -- denorm half will fit in non-denorm single
  const float half_denorm = (1.0f/16384.0f); // 2^-14
  float mantissa = ((float)(h.ieee.m)) / 1024.0f;
  float sgn = (h.ieee.s)? -1.0f :1.0f;
  sng.f = sgn*mantissa*half_denorm;
}
else if ( (h.ieee.e==31) && (h.ieee.m==0) ) { // infinity
  sng.ieee.e = 0xff;
  sng.ieee.m = 0;
}
else if ( (h.ieee.e==31) && (h.ieee.m!=0) ) { // NaN
  sng.ieee.e = 0xff;
  sng.ieee.m = 1;
}
else {
  sng.ieee.e = h.ieee.e+112;
  sng.ieee.m = (h.ieee.m << 13);
}

return sng.f;

}

inline static unsigned short ftoh(float val) {
ieee_single f;
f.f = val;
nv_half_data h;

h.ieee.s = f.ieee.s;

// handle special cases

const float half_denorm = (1.0f/16384.0f);

if ( (f.ieee.e==0) && (f.ieee.m==0) ) { // zero
  h.ieee.m = 0;
  h.ieee.e = 0;
}
else if ( (f.ieee.e==0) && (f.ieee.m!=0) ) {  // denorm -- denorm float maps to 0 half
  h.ieee.m = 0;
  h.ieee.e = 0;
}
else if ( (f.ieee.e==0xff) && (f.ieee.m==0) ) { // infinity
  h.ieee.m = 0;
  h.ieee.e = 31;
}
else if ( (f.ieee.e==0xff) && (f.ieee.m!=0) ) { // NaN
  h.ieee.m = 1;
  h.ieee.e = 31;
}
else { // regular number
  int new_exp = f.ieee.e-127;
  if (new_exp<-24) { // this maps to 0

h.ieee.m = 0;
h.ieee.e = 0;
}

  if (new_exp<-14) { // this maps to a denorm

h.ieee.e = 0;
unsigned int exp_val = (unsigned int) (-14 - new_exp); // 2^-exp_val
switch (exp_val) {
case 0: fprintf(stderr, "ftoh: logical error in denorm creation!
"); h.ieee.m = 0; break;
case 1: h.ieee.m = 512 + (f.ieee.m>>14); break;
case 2: h.ieee.m = 256 + (f.ieee.m>>15); break;
case 3: h.ieee.m = 128 + (f.ieee.m>>16); break;
case 4: h.ieee.m = 64 + (f.ieee.m>>17); break;
case 5: h.ieee.m = 32 + (f.ieee.m>>18); break;
case 6: h.ieee.m = 16 + (f.ieee.m>>19); break;
case 7: h.ieee.m = 8 + (f.ieee.m>>20); break;
case 8: h.ieee.m = 4 + (f.ieee.m>>21); break;
case 9: h.ieee.m = 2 + (f.ieee.m>>22); break;
case 10: h.ieee.m = 1; break;
}
}
else if (new_exp>15) { // map this value to infinity
h.ieee.m = 0;
h.ieee.e = 31;
}
else {
h.ieee.e = new_exp+15;
h.ieee.m = (f.ieee.m >> 13);
}
}

return h.bits;

}

We used half float vertex attributes in most of the GeForce FX launch demos, primarily for basis vectors (tangents, binormals, and normals)

What is benefit of using GLhalf instead of GLshort in such case?

Uhhm, floating point precision?

It was chosen primarily as a convenience for the vector channels – we have both ASCII and binary file formats, and using the half float format allowed us to use the ASCII format with floating point values and maintain straight-forward class compatibility.

However, the ability to use NaNs (or values > 1) to store per-vertex flags could be beneficial in some cases.