16-bit floats and GL_HALF_FLOAT

I am looking for / writing C ++ - an implementation of a 16-bit floating point number that will be used with OpenGL vertex buffers (texture coordinates, normal, etc.). Here are my requirements:

  • There must be 16 bits (obviously).
  • Must be loaded into the OpenGL vertex buffer using GL_HALF_FLOAT.
  • Should be able to represent numbers outside of -1.0 - +1.0 (otherwise I would just use GL_SHORT normalized).
  • It should be possible to convert to and from a normal 32-bit float.
  • Arithmetic does not matter - I only care about storage.
  • Speed ​​is not a primary concern, but correctness.

Here is what I have used so far for the 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, with which I need help. I managed to find many examples of 16-bit float implementations, but none of them mention whether they can be loaded into OpenGL or not.

What are some issues I should know about when loading a 16-bit float in OpenGL? Is there a half-float implementation that specifically solves these problems?

EDIT: for a popular query, here's how my vertex data is generated, loaded and displayed.

Here's how the data is defined in 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 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 know a pretty heavy function. MappedBuffers is just a structure containing 2 pointers, so if I pass NULL data to VertexArray::load() , I can use pointers to load data directly from a file into buffers (possibly from another stream). vertexSize is a function that returns sizeof() the particular format that I am passing, or 0 for an invalid format.

VertexHalf Structure:

 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 displayed using VertexArray , which we downloaded earlier:

 void VertexArray::draw(void) { if (ready == false) return; /* Bind our vertex array */ bindArray(); /* Draw it contents */ if (ibo == 0) glDrawArrays(prim, 0, vcount); else glDrawElements(prim, icount, iformat, NULL); unbindArray(); } 
+7
c ++ floating-point opengl
source share
3 answers

Change The most obvious error appears in the VertexHalf structure. You have a fill item. However, when you specify your glVertexAttribPointer, you specify 0 in the step, which indicates that it is tightly packed. Thus, you can either modify VertexHalf to remove the add-on, or change your glVertexAttribPointer to an 8 byte step.

I use the following DirectX-enabled class to support float16 and it works fine.

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; } /*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/ 
+11
source share

The GLM library supports types with half a float . The prefix used is "h", so when glm::vec3 is a three-element vector of floating point values, glm::hvec3 is a three-element vector of a half-field.

+4
source share

I only know the OpenEXR library with an implementation for half . It's good that the half implementation has the features you are looking for and even works with the NVIDIA CG toolkit.

The bad news is that I don’t know if the half type is compatible with the installed version of opengl (theoretically it should be), so you should do some testing before deciding to use it.

0
source share

All Articles