I'm looking for/writing a C++ implementation of a 16-bit floating point number to use with OpenGL vertex buffers (texture coordinates, normals, etc). Here are my requirements so far:
Here's what I have so far for an interface:
class half
{
public:
half(void) : data(0) {}
half(const half& h) : data(h.data) {}
half(const unsigned short& s) : data(s) {}
half(const float& f) : data(fromFloat(f)) {}
half(const double& d) : data(fromDouble(d)) {}
inline operator const float() { return toFloat(data); }
inline operator const double() { return toDouble(data); }
inline const half operator=(const float& rhs) { data = fromFloat(rhs); return *this; }
inline const half operator=(const double& rhs) { data = fromDouble(rhs); return *this; }
private:
unsigned short data;
static unsigned short fromFloat(float f);
static float toFloat(short h);
inline static unsigned short fromDouble(double d) { return fromFloat((float)d); }
inline static double toDouble(short h) { return (double)toFloat(h); }
};
std::ostream& operator<<(std::ostream& os, half h) { os << (float)h; }
std::istream& operator>>(std::istream& is, half& h) { float f; is >> f; h = f; }
Ultimately, the real meat of the class lies in the toFloat()
and fromFloat()
functions, which is what I need help with. I've been able to find quite a few examples of 16-bit float implementations, but none of them mention whether or not they can be uploaded to OpenGL or not.
What are some concerns I should be aware of when uploading a 16-bit float to OpenGL? Is there a half-float implementation that specifically addresses these concerns?
EDIT: By popular demand, here is how my vertex data is generated, uploaded, and rendered.
Here is how the data is defined within the WireCubeEntity class:
VertexHalf vertices[8] = {
vec3(-1.0f, -1.0f, -1.0f),
vec3(1.0f, -1.0f, -1.0f),
vec3(1.0f, 1.0f, -1.0f),
vec3(-1.0f, 1.0f, -1.0f),
vec3(-1.0f, -1.0f, 1.0f),
vec3(1.0f, -1.0f, 1.0f),
vec3(1.0f, 1.0f, 1.0f),
vec3(-1.0f, 1.0f, 1.0f)
};
unsigned char indices[24] = {
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7
};
va.load(GL_LINES, VF_BASICHALF, 8, vertices, GL_UNSIGNED_BYTE, 24, indices);
where va
is an instance of VertexArray. va.load
is defined as:
MappedBuffers VertexArray::load(GLenum primitive, VertexFormat vertexFormat, unsigned int vertexCount, void* vertices,
GLenum indexFormat, unsigned int indexCount, void* indices)
{
MappedBuffers ret;
/* Check for invalid primitive types */
if (primitive > GL_TRIANGLE_FAN)
{
error("in VertexFormat::load():\n");
errormore("Invalid enum '%i' passed to 'primitive'.\n", primitive);
return ret;
}
/* Clean up existing data */
clean();
/* Set up Vertex Array Object */
glGenVertexArrays(1, &vao);
bindArray();
/* Create Vertex Buffer Object */
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertexSize(vertexFormat) * vertexCount, vertices, GL_STATIC_DRAW);
if (!vertices) ret.vmap = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
/* Save variables for later usage */
prim = primitive;
vformat = vertexFormat;
vcount = vertexCount;
/* If we've been given index data, handle it */
if (indexSize(indexFormat) != 0)
{
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize(indexFormat) * indexCount, indices, GL_STATIC_DRAW);
if (!indices) ret.imap = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
iformat = indexFormat;
icount = indexCount;
}
/* Handle the vertex format */
switch (vformat)
{
case VF_BASIC:
/* VF_BASIC only has a position - a 3-component float vector */
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
break;
case VF_32:
/* VF_32 has 3 components for position, 2 for texture coordinates, and 3 for a normal.
Position is at offset 0, TextureCoordinate is at offset 12, and Normal is at offset 20 */
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_32), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, vertexSize(VF_32), (void*)12);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_32), (void*)20);
break;
case VF_BASICHALF:
/* VF_BASICHALF is very similar to VF_BASIC, except using half-floats instead of floats. */
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_HALF_FLOAT, GL_FALSE, 0, (void*)0);
break;
case VF_WITHTANGENTS:
/* VF_WITHTANGENTS is similar to VF_32, but with additional components for a Tangent. */
/* Tangent is at offset 32 */
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)12);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)20);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, vertexSize(VF_WITHTANGENTS), (void*)32);
break;
default:
error("In VertexFormat::load():\n");
errormore("Invalid enum '%i' passed to vertexFormat.\n", (int)vformat);
clean();
return MappedBuffers();
}
/* Unbind the vertex array */
unbindArray();
if (vertices) ready = true;
return ret;
}
I'ts a pretty heavy function, I know. MappedBuffers
is simply a struct that contains 2 pointers so that if I pass NULL
data into VertexArray::load()
, I can use the pointers to load the data directly from file into buffers (possibly from another thread). vertexSize
is a function that returns the sizeof()
of whichever vertex format I pass in, or 0 for an invalid format.
The VertexHalf struct is:
struct VertexHalf
{
VertexHalf(void) {}
VertexHalf(vec3 _pos) :x(_pos.x), y(_pos.y), z(_pos.z) {}
VertexHalf(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
half x, y, z, padding;
};
And finally the data is rendered using the VertexArray
we loaded earlier:
void VertexArray::draw(void)
{
if (ready == false)
return;
/* Bind our vertex array */
bindArray();
/* Draw it's contents */
if (ibo == 0)
glDrawArrays(prim, 0, vcount);
else
glDrawElements(prim, icount, iformat, NULL);
unbindArray();
}
The bfloat16 (Brain Floating Point) floating-point format is a computer number format occupying 16 bits in computer memory; it represents a wide dynamic range of numeric values by using a floating radix point.
A Half is a binary floating-point number that occupies 16 bits. With half the number of bits as float, a Half number can represent values in the range ±65504. More formally, the Half type is defined as a base-2 16-bit interchange format meant to support the exchange of floating-point data between implementations.
The float16 data type is a 16 bit floating point representation according to the IEEE 754 standard. It has a dynamic range where the precision can go from 0.0000000596046 (highest, for values closest to 0) to 32 (lowest, for values in the range 32768-65536).
Edit: The most obvious error appears in your VertexHalf structure. You have an element of padding. Yet when you specify your glVertexAttribPointer you specify a 0 in the stride which indicates it is tightly packed. So you can either change VertexHalf to remove the padding or change your glVertexAttribPointer to have a stride of 8 bytes.
I use the following class with DirectX for float16 support and it works perfectly.
Float16.h:
#ifndef THE__FLOAT_16_H_
#define THE__FLOAT_16_H_
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
extern short FloatToFloat16( float value );
extern float Float16ToFloat( short value );
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
class Float16
{
protected:
short mValue;
public:
Float16();
Float16( float value );
Float16( const Float16& value );
operator float();
operator float() const;
friend Float16 operator + ( const Float16& val1, const Float16& val2 );
friend Float16 operator - ( const Float16& val1, const Float16& val2 );
friend Float16 operator * ( const Float16& val1, const Float16& val2 );
friend Float16 operator / ( const Float16& val1, const Float16& val2 );
Float16& operator =( const Float16& val );
Float16& operator +=( const Float16& val );
Float16& operator -=( const Float16& val );
Float16& operator *=( const Float16& val );
Float16& operator /=( const Float16& val );
Float16& operator -();
};
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16::Float16()
{
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16::Float16( float value )
{
mValue = FloatToFloat16( value );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16::Float16( const Float16 &value )
{
mValue = value.mValue;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16::operator float()
{
return Float16ToFloat( mValue );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16::operator float() const
{
return Float16ToFloat( mValue );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16& Float16::operator =( const Float16& val )
{
mValue = val.mValue;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16& Float16::operator +=( const Float16& val )
{
*this = *this + val;
return *this;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16& Float16::operator -=( const Float16& val )
{
*this = *this - val;
return *this;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16& Float16::operator *=( const Float16& val )
{
*this = *this * val;
return *this;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16& Float16::operator /=( const Float16& val )
{
*this = *this / val;
return *this;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16& Float16::operator -()
{
*this = Float16( -(float)*this );
return *this;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
/*+----+ Friends +----+*/
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16 operator + ( const Float16& val1, const Float16& val2 )
{
return Float16( (float)val1 + (float)val2 );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16 operator - ( const Float16& val1, const Float16& val2 )
{
return Float16( (float)val1 - (float)val2 );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16 operator * ( const Float16& val1, const Float16& val2 )
{
return Float16( (float)val1 * (float)val2 );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline Float16 operator / ( const Float16& val1, const Float16& val2 )
{
return Float16( (float)val1 / (float)val2 );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
#endif
Float16.cpp:
#include "Types/Float16.h"
//#include <d3dx9.h>
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
short FloatToFloat16( float value )
{
short fltInt16;
int fltInt32;
memcpy( &fltInt32, &value, sizeof( float ) );
fltInt16 = ((fltInt32 & 0x7fffffff) >> 13) - (0x38000000 >> 13);
fltInt16 |= ((fltInt32 & 0x80000000) >> 16);
return fltInt16;
}
/+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+/
float Float16ToFloat( short fltInt16 )
{
int fltInt32 = ((fltInt16 & 0x8000) << 16);
fltInt32 |= ((fltInt16 & 0x7fff) << 13) + 0x38000000;
float fRet;
memcpy( &fRet, &fltInt32, sizeof( float ) );
return fRet;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
The GLM library supports half-float types. The prefix used is 'h' so where glm::vec3
is a 3 element vector of floating points values, glm::hvec3
is a 3 element vector of half-floats.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With