the Fiddler's picture

OpenTK.Math.Half

Project:The Open Toolkit library
Component:Code
Category:feature request
Priority:normal
Assigned:Inertia
Status:closed
Description

This type should provide an interface similar to IntPtr, with a methods, conversion operators and constructors that can pack/unpack floats and doubles.

I'm opening this task so we can keep track of progress.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

#1

Category:task» feature request
Inertia's picture

#2

Priority:normal» minor
Status:open» postponed

I don't think adding this type is high priority (hardware just starts to support it), but for future reference:

http://www.openexr.com/downloads.html
IlmBase 1.0.1 source code release

Extremely well documented float->half conversion, with conditionals and without lookup tables. half->float uses tables. License appears to be no problem.

the Fiddler's picture

#3

Excellent find, this makes our lives significantly easier.

Inertia's picture

#4

mhmm how do I change the settings in a way that expresses this could be a feature for 0.9.3, but definitley won't make it into 0.9.2?

the Fiddler's picture

#5

With the current setup, this is a request for a feature missing from 0.9.1. The postponed status means that it won't make it to the next version. When
it's time to implement the feature, we can just mark it as active, bump the version and start working on it.

Which means it's fine the way it is now.

(Let's move this discussion to the forums, though)

Inertia's picture

#6

I've been working on this, kinda need some input to know where this should be heading.

1. Does anyone have a better idea to obtain the bits of a float?

[StructLayout(LayoutKind.Explicit)]
    internal struct Half4ByteUnion
    {
        [FieldOffset(0)]
        public Single f;
        [FieldOffset(0)]
        public Int32 i;
    }

I've tried it with fixed-statement, but type safety kicked my a*s.

2. After some tests it proves correct that doing any arithmetic operations with half type is futile. The type can only represent 65536 unique numbers (minus special cases) and you run into precision problems very soon. Should a HalfToFloat method be provided at all? Using it for debugging now, but I think anyone should be advised to store 32-bit float copies for arithmetics and use 16-bit only for certain attributes.

3. Currently the internal representation of the half (Uint16) is public. This is very dangerous, because this field is not intuitive to edit, but I see no alternative since it must be addressable for upload to GL. Any suggestions how to deal with this? (I've added a summary to the field to point that problem out)

4. I'm manually throwing ArithmeticExceptions if weird numbers like NaN or +/- infinity are passed. Half can represent those numbers, but I find it better to throw when encountering them, because these numbers are no good and probably some error in other calculations. Should it stay like that, or not throw and let the user deal with the problem?

PS: I'll attach an example app later on, kinda cleaning my debug mess up and make it somewhat usable without reading full source code 1st.

the Fiddler's picture

#7

1. Either this, or pointer cast in an unsafe context:

int GetBits(float f)
{
    unsafe
    {
        fixed (float* ptr = &f)
        {
            return *(int*)ptr;
        }
    }
}

Edit: No idea if this is faster, though.

2. I think yes, as an explicit conversion operator and a ToSingle() method. We should probably follow the API of IntPtr (which is used in a somewhat similar way).

3. Make it internal. This way, we can use it (e.g. to add overloads to the GL class), while keeping it safe from the user.

Edit:
4. I'd say let the user deal with it. There should be an API to query for these conditions (e.g. IsNan, IsInfinity) - just like the Single class.

Inertia's picture

#8

1. I'm not certain either which is faster, but my guess is the compiler has better chances with the struct.

2. Currently there are only 2 public methods:

public void FromSingle( Single f );
public Single ToSingle();

There are some MinValue constants, and I'll take your suggestion to add IsNaN, IsPositiveInfinity properties.

3. I failed to explain it properly, you will understand when you see code.

4. Well the C API itself had a function in it that would create an arithmetic overflow (which I ripped out and throw an exception instead). I don't think this should be removed, but I may ofcourse stop intercepting NaN/Infinity.
The idea was: you have a 3D model built out of floats and you want to compact normals and texcoords into half type, you could simply put the whole conversion into a try block, rather than converting and afterwards looping over the result and test individually for errors. If such an error occurs you might want to try a different vertex format (without Half), that's why the try block would make this easier.
Adding IsNaN property etc. should be easy.

------------------

Here's a x86 console app that let's you enter floating point numbers, converts them to Half, prints binary representation and then converts back to float.
I've removed all need for any lookup tables, because I don't see any reason why OpenTK should bother with a 256kb table just for Half type, which probably 10% of the users will use at all.
It works great for me, but I'd like to hear whether this causes any troubles with different endianess (which it shouldn't) or x64 OS.

AttachmentSize
OpenTK Half v1.rar9.54 KB
Inertia's picture

#9

Priority:minor» normal
Assigned to:Anonymous» Inertia
Status:postponed» open

Added IsZero, IsNaN, IsPositiveInfinity and IsNegativeInfinity Properties. Removed all exceptions besides the FPU overflow.

the Fiddler's picture

#10

Priority:normal» minor
Assigned to:Inertia» Anonymous
Status:open» postponed

Comments on the code:

  1. The FromSingle() method isn't necessary - the constructor does exactly that.
  2. I can't really see why the _internalBits field should be public.
  3. Library interfaces should avoid public constants - use a static readonly field instead.

The following attachment implements these changes and adds a few simple conversion operators. Does it make sense to have fast Half<->Int or any Half<->Double conversions?

Edit: It seems you forgot to attach your updates..?

AttachmentSize
Half.7z5.42 KB