Godot: Add support for double precision floats

Created on 14 Apr 2014  Â·  78Comments  Â·  Source: godotengine/godot

Since I need more number precision for my game, I tried to recompile the engine with REAL_T_IS_DOUBLE, however it doesn't work.

It seems like float have been used here and there in place of the abstract real_t type.
In the other hand b2ConvexDecomp::float32 is defined as real_t... and real_t can be a double precision float (float64). So it is quite confusing.

archived feature proposal core

Most helpful comment

We discussed this on IRC today when reviewing #27665 (see logs from 14:53).

The plan is to handle this for 4.0, as we'll have the opportunity to change core APIs to relying on doubles and Vulkan should make it easier to support for rendering than OpenGL ES.

All 78 comments

support for doubles was never completed, maybe one day..

On Mon, Apr 14, 2014 at 7:23 AM, m4nu3lf [email protected] wrote:

Since I need more number precision for my game, I tried to recompile the
engine with REAL_T_IS_DOUBLE, however it doesn't work.

It seems like float have been used here and there in place of the abstract
real_t type.
In the other hand b2ConvexDecomp::float32 is defined as real_t... and
real_t can be a double precision float (float64). So it is quite confusing.

—
Reply to this email directly or view it on GitHubhttps://github.com/okamstudio/godot/issues/288
.

See also the proposed implementation in #334.

That's planned for 3.0 AFAIK.

real_t seems to be just typedef for float so it can be 64-bit, depending on your platform/compiler.

C++ standard:

There are three floating point types: float, double, and long double. The type double provides at least
as much precision as float, and the type long double provides at least as much precision as double. The
set of values of the type float is a subset of the set of values of the type double; the set of values of the type
double is a subset of the set of values of the type long double. The value representation of floating-point
types is implementation-defined. [ Note: This International Standard imposes no requirements on the accuracy
of floating-point operations; see also 18.3.2. — end note ] Integral and floating types are collectively called
arithmetic types. Specializations of the standard library template std::numeric_limits (18.3) shall specify
the maximum and minimum values of each arithmetic type for an implementation.

So if you want to be sure, you have to check:

std::assert( sizeof(float) == 4 && std::numeric_limits<float>::is_iec559 );

( this will work with C++98 ).

Wasn't this fixed in 3.0 already?

it's not done yet, needs some more work and testing

On Sat, Mar 11, 2017 at 8:23 AM, Bojidar Marinov notifications@github.com
wrote:

Wasn't this fixed in 3.0 already?

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/godotengine/godot/issues/288#issuecomment-285860764,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AF-Z23LdNaXNGndPbxAPEWlTK6Q-MAQrks5rkoQbgaJpZM4ByNZg
.

Not gonna be done for 3.0

What is the current status? What is working? What is missing? How much work is required to get this in?

I'm not completely sure what needs to be done for this feature (and adding a proper compilation flag for this) happens, but comes to mind:

1) There is some support for using doubles in binary serialization, but it's probably not complete. files such as resouce_format_binary.cpp and marshalls.cpp would need to be checked and made sure that doubles are written (and read) in a binary compatible way.
2) Variant needs to be tested for double support, as this would mean Vector2, Vector3, etc. are doubles so Variant is bigger
3) Some third party libraries might need to be modified (I think probably not, but i'm not sure, If Bullet is added, i think it can also use btScalar as double)
4) Need to check that everything works in VisualServer when using doubles
5) As it is not possible to use doubles in GLSL, world coordinates in matrices will need to be somehow wrapped or clamped to avoid numerical precision errors. This is most likely the last step that needs to be done once everything works

If you or anyone else would like to do this, feel welcome :) It's quite some work but it would be awesome to get it to work

As it is not possible to use doubles in GLSL

Bzzzzt wrong @reduz. I've used them in my project few years ago.

You can check on OpenGL wiki. But I remember I had to use some extension for it on my card:

#extension GL_ARB_gpu_shader_fp64 : require

https://github.com/Hopetech/libSoftFloat

Emulated support for fp64 library.

I'll add a compilation flag called float and it'll have float=32 and float=64 as arguments. Hopefully we won't have to implement float=128 ;)

Hello godot dev,

I replayed to question about using libSoftFloat in your game engine: Hopetech/libSoftFloat#1
TL;DR: Yes, you can use it. But the amount of work is huge and the result will be slow.

Let me know if you have any questions.

I am also interested in double-precision floats in Godot. Please implement this if you can.

If this doesn't get completed, as a workaround you'll be able to keep track of double precision floats in C# (and I believe in C++ also) for scripting. For instance, if it's for something like avoiding jittery movement for large scale (planetary sized) games then keeping track of x,y,z as doubles then converting them to floats when setting the position usually does a good job. It's not ideal, but it's an option.

@fire 128-bit? Well, that depends on what kind of games you expect Godot to power.

  • 32-bit floats have 6-7 decimal digits of accuracy, so if you aim for millimeter-precision, the play area is limited to just a few kilometers. This is incredibly restrictive, and means any large scale game is forced to use relative positioning, which is a pain especially for multiplayer games.

  • 64-bit floats have 15-16 decimal digits of accuracy, so if you aim for millimeter-precision, the play area is limited to a few billion kilometers, or a few terameters. This is accurate enough to have non-relative positioning on planets, but not beyond solar systems (Neptune is about 5 Tm orbital radius). For universe scale games, it could be useful to have 128-bit floats.

  • 128-bit floats have 33 decimal digits of accuracy, so if you aim for micrometer-precision, the play area is limited to a few septillion kilometers, or thousands of yottameters. This is almost exactly the estimated diameter of the observable universe. In other words, 256-bit floats are never needed :) But also, very few pieces of harwdare and programming languages support 128-bit floats.

@aaronfranke btw. floats are not evenly distributed.

Precision depends on how far you are from the first number that can be represented (the first one after underflow) - http://blogs.mathworks.com/images/cleve/floatguijpeg.jpeg

Yes, I know. A float may be able to be 1.000001 or 1000.001 but not 1000.000001.

The point is that with 64-bit floats you can have 1000000000000.001 (in decimal). Which is ideal for pretty much everything except for galaxy-scale or out-of-solar-system games, in which case 128-bit would be nice to have, and 256-bit is never needed even for simulating the whole universe.

@aaronfranke For Universe-large scale simulations the way to go would be to implement real_t as a very big fixed decimal. This would require the physics engine to support this kind of real_t.
GodotPhysics uses real_t everywhere but not sure how this will play with Bullet.
Also coordinates will need to be converted to float/double for rendering, relative to the camera (to minimize precision errors close to the camera).
I guess it is quite some work.

Now that C# is out and stable, I would like to point out that C# does not have a direct equivalent to typedef. We would have to use compiler flags to use an abstract real_t type (#if THING, #else, #endif). These would need to be placed at the beginning of every C# class.

Otherwise, built-in classes such as Vector3 would have to be re-implemented with doubles (we could call it Vector3d etc) if we want double easily implementable in C#.

@aaronfranke That's an interesting question :) I wonder if we could implement it with generic types that are constrained to IComparable?

``` C#
public class Vector3Base : where T is IComparable // or IComparable?
{
T x;
T y;
T z;

// The rest of the Vector3 stuff here...

}

``` C#
public class Vector3 : Vector3Base<float> {}

C# public class Vector3d : Vector3Base<double> {}

Or something like that?

I think it's pointless, double solves the problem.

On Feb 3, 2018 1:24 PM, "Nathan" notifications@github.com wrote:

@aaronfranke https://github.com/aaronfranke That's an interesting
question :) I wonder if we could implement it with generic types that are
constrained to IComparable?

public class Vector3Base() : where T is IComparable
{
T x;
T y;
T z;

// The rest of the Vector3 stuff here...

}

public class Vector3 : Vector3Base {}

public class Vector3d : Vector3Base {}

Or something like that?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/godotengine/godot/issues/288#issuecomment-362802391,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AF-Z2_W7GkxRWpS6roBSicDNB3qQ3mFpks5tRE_1gaJpZM4ByNZg
.

So, is the plan to make the entire engine double (and no float option), or do you just mean C# for the mono side of things so it's compatible with both a float and double version of Godot?

I would be 100% fine with exclusively doubles. But I thought that others preferred a dual-mode, which is useful for C# script compatibility with Unity scripts and people who care about 1% performance increases with float for small-scale games.

And also, it is probably harder to implement exclusively doubles without a stepping stone of supporting both (the engine would be uncompilable for awhile during a direct conversion to double, right?).

After quite a bit of thought over the last few weeks I'm not persuaded one way or the other and am fine with having Godot exclusively double or dual builds, one with float and the other with double.

@aaronfranke and I were discussing this on Reddit a couple of weeks ago, him being for going exclusively double and me being for a double version, but there would still be a primary float based version, these were my two main thoughts why I was against and then why I don't think it's as big of a deal anymore:

A) Using double 'doubles' the RAM usage everywhere real_t is used including meshes and animation data. This is the bigger of my two concerns, and:

B) It eliminates the ability to use SIMD instructions.

And now why after some though I don't care one way or the other:

A) The mesh memory being doubled is a slight concern, but textures usually use the most RAM and AFAIK they wouldn't be affected by such a change. Also, (I'm not saying this is a good idea, but) I think meshes could technically still use floats since the main thing that really matters is the transform using doubles.

B) Does Godot even use SIMD instructions anywhere? I'm not really sure and have to admit that I haven't looked that deeply to notice. If not already implemented it mostly negates my point B unless someone is willing to implement them.

Ultimately I think I would be happy if it went either way, exclusively double or not.

Big flip on my end from our Reddit discussion, right @aaronfranke? :D

Building on that, the only way we can test real-world performance is a dual-implementation. I believe the performance hit will be very insignificant (pun intended) while @NathanWarden was starting to convince me that it might actually be significant.

In the end, however, I personally am fine with either dual builds or exclusively double.

Okay, I took a quick search in the source for SIMD and it looks like everywhere they are used are all in third partly libraries, mostly Bullet, in which case I presume we wouldn't touch the source code anyway?

The only Godot specific code that even mentions it is in the Android "cpu_features" header and source file which it seems might just for letting you know that the CPU supports it.

That's a tricky one though, because single precision physics in many cases would negate the benefit of having double everywhere else. But, using double in physics would be a pretty big performance impact since it does negate any benefit from SIMD instructions and would probably negate any performance benefit we just got from switching to it.

Hmmmm? :/

Last conversation I had with others said that it's possible to tell bullet to use double precision.

@fire You're right, it does allow for both.

@aaronfranke I suppose you may be right about that and would be worth running some tests on.

Can someone come with a design that works on 32bit rendering aka the position matrix in 32bit?

I had trouble doing for example [64] bit matrixes for a 64 bit position without using opengl 4, which is possible, but would require another renderer. [Someone mentioned a 64 bit down convert.]

The rest of the engine is straightforward but tedious to convert. This includes editor code and physics code.

Reference:

Origin rebasing in UnrealEngine: https://github.com/EpicGames/UnrealEngine/pull/2359

Edited.

@fire Pardon my ignorance, but are you referring to passing the matrix in Basis to the renderer?

This line. https://github.com/fire/godot/blob/master/drivers/gles3/shader_gles3.cpp#L98

Represents sending location to the graphics api.

If we are unable to use doubles for rendering we could always convert to a single precision float relative to the camera. But I am pretty sure we can use doubles for rendering. If we need at least OpenGL 3.2* for double precision rendering then it would make sense to keep both single and double precision builds of Godot. OpenGL 3.2 is fairly old, it's the equivalent of DX10, most hardware will support it.

* You @fire say OpenGL 4 but the article you linked says 3.2

Awesome, thanks for pointing to that :)

I could be wrong, but can't we just cast it to float here? I'm not sure how much of a performance impact that would be, but accuracy shouldn't be an issue since that really only matters when doing math and not when reading the values.

This is only for cameras too, right? There usually won't be more than a small handful of those active at a time.

We use OpenGL ES 3.0. So implementing OpenGL 3.x is another renderer. It's not that big, just a copy and paste. Look at the size of the opengl es 3.0 folder. It's not very big.

Yep, GLES 3, and GLES 2 is being worked on by Thomas, so double will need to work with that as well.

So, it looks like we'd just make a jagged array of float[4][4], cast the camera matrix to it, then pass that matrix to it.

IE, something like this?

#ifdef REAL_T_IS_DOUBLE
float float_matrix[4][4];
real_t cam_matrix[4][4] = C->get().matrix; // Not sure if this would be faster or slower to have

for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
        float_matrix[i][j] = (float)cam_matrix[i][j];
    }
}
glUniformMatrix4fv(location, 1, false, &(float_matrix[0][0]));
#else
glUniformMatrix4fv(location, 1, false, &(C->get().matrix[0][0]));
#endif

[if you casted it's the same as using 32 bit floats.]

That won't work. The use case is having more than 8km maps. If the camera location is at 8km. It'll visibly jitter as mentioned in the Unreal Engine thread.

If you use a few transforms, for instance world, to character model, to weapon scope, the precision will be less than that.

The origin rebase trick relies on pretending the center (0,0,0) of the world is where the player is and everything is else is rebased as if the origin of the world was at the player camera. This stopped jitter and precision error teleportation.

If a triangle vertex is outside the 8km rebased area it is probably going to teleport short distances / not move when moved.

Well, you can still have a structure where a parent node which acts as the "origin" is moved so that the player is at the center. Something like this:

worldObject.transform.position -= playerObject.transform.position;

(where player is a child of world) I've tried a system like this in Unity. This can help improve rendering but still results in jagged positions. However, if the positioning is 64-bit precision it would work very well. It's also super easy to implement and still allows a shared global origin for all players.

However, it sounds like the best solution, then, would be to support doubles for rendering in OpenGL 4.0 but not for lower versions as they don't support them. I would suggest using OpenGL 4.1 due to MacOS.

@fire Were you just referring to the origin rebase trick not working, or also casting the camera matrix as float as not working?

Casting the camera matrix as float won't work.

Splitting the world into zones of 8km might work though, but that is not simple.

Does C# support operator overloading?
I still think that for universe scale simulations the way to go is a fixed point decimal format (basically big integers). The phyiscs server should convert these big integer in some float/doubles relative to some origin (like the contact point for a body pair) and operate there, then convert the result back in absolute coordinates. (I don't think bullet supports this but I can implement it in the Godot Physics engine farily easily).
Similarly for the rendering server. Before passing data to the rasterizer, everything is converted in floats relative to the camera origin.
I guess we can have a look at the code of Celestia https://celestia.space/ for the rendering part at least.
Of course all of this should be optional at compile time to not impact performaces of games that do not need such a big scenario.
Thoughts?

@fire I see what you're saying... I just put together a scene that moves with double and casts to float for the position. Granted, it doesn't start jittering at 8k, but more like 300k, but still is pretty bad once it get's into those numbers, which are somewhat small for what someone might want to do.

I could see my method working reliably maybe up to almost 100k, but nothing beyond that.

@m4nu3lf What's your approach for:

Similarly for the rendering server. Before passing data to the rasterizer, everything is converted in floats relative to the camera origin.

@NathanWarden That's an improvement to 8km maps, I could cast the rasterization pipeline to float and have the rest of the engine in doubles. This includes the physics engine. This seems straightforward to do right away.

Later, we can think of a way to get bigger than 100k. 100K means 50k x 50k right? Ranging from -50km to 50km. When I said 8km, it's actually 4x4 km that goes from -4km to 4km.

So far approaches we have are:

  • cast double to float for rasterization
  • rebase origin
  • fp64 using opengl 3.2
  • multiple zones of 100k or 8k maps

@fire we take the matrix of an object where each element is a big integer (optimizations are possible as rotations don't need such precision) and remove the position of the camera from the origin of the object, then cast everything to float. Rendering will assume the camera is always at coordinates (0, 0, 0)

How much work is required on the non-rendering side of things? Any more floats that should be replaced with real_t? What about a C# implementation? We could get typedef added to C# itself, or

#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
using real_t = System.Single;
#endif

at the start of every file (this is the closest thing to typedef), or another alternative, as @NathanWarden suggested, use a generic class and extend it where needed:

So far git log says:

  • 63 files changed, 488 insertions(+), 463 deletions(-)

Non rendering is straight-forward though.

@reduz How do you use a Vector where real_t is a double? It fails in method binding.

#ifdef PTRCALL_ENABLED
    virtual void ptrcall(Object*p_object,const void** p_args,void *r_ret) {

        T *instance=Object::cast_to<T>(p_object);
         (instance->*method)(PtrToArg<P1>::convert(p_args[1-1]), PtrToArg<P2>::convert(p_args[2-1]))  ;
    }
#endif
    MethodBind2 () {
#ifdef DEBUG_METHODS_ENABLED
        _set_const(false);
        _generate_argument_types(2);
#else
        set_argument_count(2);
#endif

@fire I can't say my test were the most thorough tests, but I was getting about -100k to 100k casting positions. I was just moving it forward before but I updated it a minute ago to go diagonally and it begins jittering a little bit sooner.

Anyway, here's the project in case you want to do further tests and double check me before trying anything internal. :)

TestingPlayground.zip

I don't use c#. Is impossible to convert to gdscript?

[edited]

Ah, that's an interesting way to test.

Is there a double type in GDScript? If there is then I can try it. Otherwise, maybe @aaronfranke can try it and see, I think he uses C#.

To help out the c++ conversion add float=64 to the scons compile. Remove float=64 to compile standard godot. Both editions must work.

Here's how to clone my repository.

git clone -b double https://github.com/fire/godot.git

https://github.com/godotengine/godot/pull/12299

I'm stuck on using Vector and classdb integration.

Won't be able to help much during the next week, but I've posted the work done in the pull request.

https://outerra.blogspot.ca/2012/11/maximizing-depth-buffer-range-and.html for reference on dirty math tricks :)

https://outerra.blogspot.ca/2014/05/double-precision-approximations-for-map.html

https://godotengine.org/article/abandoning-gles3-vulkan-and-gles2

Godot is moving to Vulkan. This may impact how we implement double precision camera matrixes @fire

@aaronfranke The same thing had crossed my mind. Does this mean it's possible to skip on the workarounds we were discussing?

I would still really like to see this in 3.1.
I was thinking of adding here a list of things I believe may be missing,
besides shader code.

On Feb 27, 2018 18:13, "Nathan" notifications@github.com wrote:

@aaronfranke https://github.com/aaronfranke The same thing had crossed
my mind. Does this mean it's possible to skip on the workarounds we were
discussing?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/godotengine/godot/issues/288#issuecomment-369027789,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AF-Z2-cTRtd84FfLgnRQVqCLUuZSn6ceks5tZG_pgaJpZM4ByNZg
.

In C# there is not a direct equivalent to typedef however we can accomplish the result via using and #if compiler declarations. I placed this at the start of Mathf, Vector3, Vector2, Quat, and Basis, with float replaced with real_t everywhere, and it works perfectly.

#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
using real_t = System.Single;
#endif

As far as I can tell, the SPIR-V language has floats being 64 bit defined. Then a float (of a certain size) is generically defined for matrixes.

So floats being 64 bit should work with Vulkan.

https://github.com/godotengine/godot/pull/17134

I've abstracted float to real_t for the C# code. Probably doesn't compile with doubles yet, but this patch is a stepping stone towards double support in Godot C#.

What is the status of this work?

Feature freeze right now. I have a PR open for making some progress towards it, for after 3.1 is released 4.0.

If it's not rude, I'd like to express my wholehearted support for this effort! Could we have it for 3.2? (I wish I could help but this is way above my current C++ ability.)

I've been building complex workarounds for my solar system simulator. I did figure out how to keep the camera as the center of the universe to prevent Pluto from shaking wildly. However, a more pernicious problem is related to time. Body positions, velocities, and current rotation states are all calculated from cumulative "sim time". Some things like planet rotation starts to get very jittery only 100 years from epoch time due to imprecision in my cumulative sim time variable.

@aaronfranke I'm working on a similar simulator.

We discussed this on IRC today when reviewing #27665 (see logs from 14:53).

The plan is to handle this for 4.0, as we'll have the opportunity to change core APIs to relying on doubles and Vulkan should make it easier to support for rendering than OpenGL ES.

Since the float in GDScript is now 64bit on the master branch, is this resolved?

@asheraryam No, it is not resolved. float in GDScript has been 64-bit double-precision for years, the issue is with having core types (like Vector3) and other CPU-side math done with doubles.

I have a PR here: https://github.com/godotengine/godot/pull/21922

@aaronfranke You mention CPU-side, but what about GPU-side? I'm working on shaders for my game and I'm noticing some issues with very small floating point values being rounded down to zero...

@blaeberry As far as I know, using FP64 (= double precision) in shaders is ill-advised in a game engine. This is because FP64 can be very slow on consumer-grade GPUs. 64-bit floating point performance is intentionally crippled on such GPUs, to incite professionals to buy much more expensive professional-grade GPUs.

How do other engines typically handle this? I'm curious to see how they do it :slightly_smiling_face:

@blaeberry As far as I know, using FP64 (= double precision) in shaders is ill-advised in a game engine. This is because FP64 can be very slow on consumer-grade GPUs. 64-bit floating point performance is intentionally crippled on such GPUs, to incite professionals to buy much more expensive professional-grade GPUs.

How do other engines typically handle this? I'm curious to see how they do it 🙂

There is a trick to bypass this problem.Although we use FP64, we can convert the coordinate system from world space to the camera-relative coordinate system when calculating animation and final rendering, so that we will not use expensive FP64 in shaders

I've started a similar discussion on Unity forums. You might find some valuable information there.

As part of an effort to move all feature proposals to the godot-proposals repo, I wrote this proposal which directly supersedes this issue. It significantly summarizes and elaborates on the discussion about double support in Godot. As such, this issue will now be closed.

Will 4.0 support 128-bit floats/doubles?

@3top1a There are no plans to add 128-bit (or arbitrary precision) integers or floats to GDScript, but this could be done by a third-party module.

Godot does have a fixed point 128bit doubles implementation, but no plans to add to gdscript.

Edited:
https://github.com/godotengine/godot/blob/988dd09047aab9f33b172cfb140d4081885c7083/thirdparty/misc/r128.h

@fire well yes, but that's a 128-bit integer.
What about a 128-bit float?

(in the core since I'm coding in c++ modules now)

I was imprecise, its 64.64 float. It is not a 128 bit integer.

Was this page helpful?
0 / 5 - 0 ratings