While loading a model I get a big array of 32 bit floating point texture coordinates. I just upload that to a single vertex buffer (along with other data, such as vertices, normals, etc.) and use it while rendering via


I use this format for all types of meshes.

However, 16 bit and the ~65000 possible values are more than enough to represent textures that are mostly smaller than 4096 in size per dimension.

Now I'm wondering how to most effectively switch to some 16 bit based texture coordinate format, with respect to maximizing performance, i.e. minimizing the bandwidth and storage used, as well as minimizing ALU operations. Mainly I'm wondering:

  • Should I use half floats or short integers?
  • How do I convert the 32 bit floats I get when loading the model safely to the respective 16 bit format and upload it to the vertex buffer? In C++ or other languages, by rolling some custom type conversion via bit twiddling, or use standard procedures provided by the language or libraries such as GLM?
  • How do I have to change the glVertexAttribPointer call to use that format, and does the input in the vertex shader stay a "vec2"?
  • Is there maybe another option to save space here? For example, most textures are in the range of 256x256 to 1024x1024 range. 16 bits are way too much for that, but 8 bit are not enough for most textures. Are there some other strategies to save space here?

3 Answers 3


GLSL does not give support to operate on 16 bit types, unless for compatibility with OpenGL ES, which does not change type functionality. However, you could use uint packHalf2x16(vec2 v) to encode two 16 bit as an uint. Done that you could use vec2 unpackHalf2x16 (uint v) to convert the values back from the encoded representation. You should use this function in the beginning of your shader to acquire the coordinates.

  • \$\begingroup\$ What about lowp and mediump? \$\endgroup\$
    – TravisG
    Dec 16, 2013 at 13:54
  • \$\begingroup\$ "Precision qualifiers are added for code portability with OpenGL ES, not for functionality. They have the same syntax as in OpenGL ES, as described below, but they have no semantic meaning, which includes no effect on the precision used to store or operate on variables." (OpenGL Shading Language Spec, 4.7). \$\endgroup\$ Dec 16, 2013 at 14:18
  • \$\begingroup\$ But your point is valid, since I have wrote the answer in a confuse way. I will fix it. \$\endgroup\$ Dec 16, 2013 at 14:24
  • \$\begingroup\$ Thanks for that information. Weird, when I come across various shader implementations of stuff in papers, it's usually in HLSL and people use half floats there all the time (supposedly for performance benefits), but that's perhaps because it's often console code and the graphics architecture there has specific ALUs that work with half floats. Edit: Does OpenGL (3.x +) support client-side (as in, C++/whatever side of code) half floats? I see that you can hand gl_half_float to some functions, i.e. glVertexAttribPointer. \$\endgroup\$
    – TravisG
    Dec 16, 2013 at 17:15
  • \$\begingroup\$ You can use GL_HALF_FLOAT in glVertexAttribPointer: opengl.org/sdk/docs/man3/xhtml/glVertexAttribPointer.xml. But the values will arrive as floats in your shader if you are not in OpenGL ES. \$\endgroup\$ Dec 18, 2013 at 19:43

the easy option to integrate is to store 2 texture coordinates into one float using the int part and the decimal part:

//c++ code:
float       packCoord(const vec2 & tc)
    return floorf(tc.x * 1000.0f)+tc.y * 0.1;
//shader code
mediump vec2    UnpackCoord(highp float f)
    highp float fr = fract(f);
    return vec2( (f-fr)/1000.0, fr * 10.0);

This method stores the first value in the integer part (by multiplying by 1000) of the float, and the second value in the fractional part. Using the magic number 1000 allows you reliably to store values from 0 up to 9.999 and for texture coordinates this is more than enough. For a 2028 texture there is almost no precision loss using this method compared to the classic approach.

The computing cost if the unpacking is unnoticeable.

  • 1
    \$\begingroup\$ I'd use 1024.0 as a multiplier if only so rounding errors are minimized \$\endgroup\$ Dec 16, 2013 at 13:07
  • \$\begingroup\$ @ratchet freak This method can be adapted , but i'm not sure how multiplying with 1024 would work with floating points representation. I used this to save some memory and in my test case the coord was for a 2048 texture and using 1000 gave good results. \$\endgroup\$
    – Raxvan
    Dec 16, 2013 at 13:10
  • \$\begingroup\$ a multiply by 1024 can be optimize to an integer addition by just playing with the exponent and will leave the mantissa untouched \$\endgroup\$ Dec 16, 2013 at 14:09
  • \$\begingroup\$ @ratchet freak i think you are right if i think about this. \$\endgroup\$
    – Raxvan
    Dec 16, 2013 at 14:11

The most effective solution ended up being just handing half floats on the C++ side (by using the GLM "half" data type, which is a 16 bit sized float) and just changing the one parameter in glVertexAttribPointer to GL_HALF_FLOAT instead of GL_FLOAT.

It's probably even more efficient to hand over normalized shorts or something like that, since they have "even spacing" in terms of precision, which is exactly what's perfect for texture coordinates, but I do not have time right now to try that out since the current solution is working perfectly, and I'm not even sure if half floats have holes big enough in their precision to miss a couple of texture coordinates in a (for example) 4096x4096 texture.

On another note, I did this for all data types where 16 bit floats are probably good enough (colors, normals, etc.) and it promptly upped the FPS I get on low end hardware by about 66% (in scenes that were heavy on vertices, and not so heavy on fragment operations), so it was definitely worth it.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .