Browse Source

Enable floating point overflow exceptions (#1398)

This helps find programming errors.
Jorrit Rouwe 8 months ago
parent
commit
fe87468daa

+ 20 - 6
Jolt/Core/FPException.h

@@ -16,11 +16,12 @@ JPH_NAMESPACE_BEGIN
 class FPExceptionsEnable { };
 class FPExceptionsEnable { };
 class FPExceptionDisableInvalid { };
 class FPExceptionDisableInvalid { };
 class FPExceptionDisableDivByZero { };
 class FPExceptionDisableDivByZero { };
+class FPExceptionDisableOverflow { };
 
 
 #elif defined(JPH_USE_SSE)
 #elif defined(JPH_USE_SSE)
 
 
-/// Enable floating point divide by zero exception and exceptions on invalid numbers
-class FPExceptionsEnable : public FPControlWord<0, _MM_MASK_DIV_ZERO | _MM_MASK_INVALID> { };
+/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers
+class FPExceptionsEnable : public FPControlWord<0, _MM_MASK_DIV_ZERO | _MM_MASK_INVALID | _MM_MASK_OVERFLOW> { };
 
 
 /// Disable invalid floating point value exceptions
 /// Disable invalid floating point value exceptions
 class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MASK_INVALID> { };
 class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MASK_INVALID> { };
@@ -28,10 +29,13 @@ class FPExceptionDisableInvalid : public FPControlWord<_MM_MASK_INVALID, _MM_MAS
 /// Disable division by zero floating point exceptions
 /// Disable division by zero floating point exceptions
 class FPExceptionDisableDivByZero : public FPControlWord<_MM_MASK_DIV_ZERO, _MM_MASK_DIV_ZERO> { };
 class FPExceptionDisableDivByZero : public FPControlWord<_MM_MASK_DIV_ZERO, _MM_MASK_DIV_ZERO> { };
 
 
+/// Disable floating point overflow exceptions
+class FPExceptionDisableOverflow : public FPControlWord<_MM_MASK_OVERFLOW, _MM_MASK_OVERFLOW> { };
+
 #elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
 #elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
 
 
-/// Enable floating point divide by zero exception and exceptions on invalid numbers
-class FPExceptionsEnable : public FPControlWord<0, _EM_INVALID | _EM_ZERODIVIDE> { };
+/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers
+class FPExceptionsEnable : public FPControlWord<0, _EM_INVALID | _EM_ZERODIVIDE | _EM_OVERFLOW> { };
 
 
 /// Disable invalid floating point value exceptions
 /// Disable invalid floating point value exceptions
 class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID> { };
 class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID> { };
@@ -39,6 +43,9 @@ class FPExceptionDisableInvalid : public FPControlWord<_EM_INVALID, _EM_INVALID>
 /// Disable division by zero floating point exceptions
 /// Disable division by zero floating point exceptions
 class FPExceptionDisableDivByZero : public FPControlWord<_EM_ZERODIVIDE, _EM_ZERODIVIDE> { };
 class FPExceptionDisableDivByZero : public FPControlWord<_EM_ZERODIVIDE, _EM_ZERODIVIDE> { };
 
 
+/// Disable floating point overflow exceptions
+class FPExceptionDisableOverflow : public FPControlWord<_EM_OVERFLOW, _EM_OVERFLOW> { };
+
 #elif defined(JPH_CPU_ARM)
 #elif defined(JPH_CPU_ARM)
 
 
 /// Invalid operation exception bit
 /// Invalid operation exception bit
@@ -47,8 +54,11 @@ static constexpr uint64 FP_IOE = 1 << 8;
 /// Enable divide by zero exception bit
 /// Enable divide by zero exception bit
 static constexpr uint64 FP_DZE = 1 << 9;
 static constexpr uint64 FP_DZE = 1 << 9;
 
 
-/// Enable floating point divide by zero exception and exceptions on invalid numbers
-class FPExceptionsEnable : public FPControlWord<FP_IOE | FP_DZE, FP_IOE | FP_DZE> { };
+/// Enable floating point overflow bit
+static constexpr uint64 FP_OFE = 1 << 10;
+
+/// Enable floating point divide by zero exception, overflow exceptions and exceptions on invalid numbers
+class FPExceptionsEnable : public FPControlWord<FP_IOE | FP_DZE | FP_OFE, FP_IOE | FP_DZE | FP_OFE> { };
 
 
 /// Disable invalid floating point value exceptions
 /// Disable invalid floating point value exceptions
 class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { };
 class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { };
@@ -56,6 +66,9 @@ class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { };
 /// Disable division by zero floating point exceptions
 /// Disable division by zero floating point exceptions
 class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { };
 class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { };
 
 
+/// Disable floating point overflow exceptions
+class FPExceptionDisableOverflow : public FPControlWord<0, FP_OFE> { };
+
 #elif defined(JPH_CPU_RISCV)
 #elif defined(JPH_CPU_RISCV)
 
 
 #error "RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled."
 #error "RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled."
@@ -76,6 +89,7 @@ class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { };
 class FPExceptionsEnable { };
 class FPExceptionsEnable { };
 class FPExceptionDisableInvalid { };
 class FPExceptionDisableInvalid { };
 class FPExceptionDisableDivByZero { };
 class FPExceptionDisableDivByZero { };
+class FPExceptionDisableOverflow { };
 
 
 #endif
 #endif
 
 

+ 3 - 2
Jolt/Geometry/AABox.h

@@ -34,10 +34,11 @@ public:
 		return box;
 		return box;
 	}
 	}
 
 
-	/// Get bounding box of size 2 * FLT_MAX
+	/// Get bounding box of size FLT_MAX
 	static AABox	sBiggest()
 	static AABox	sBiggest()
 	{
 	{
-		return AABox(Vec3::sReplicate(-FLT_MAX), Vec3::sReplicate(FLT_MAX));
+		/// Max half extent of AABox is 0.5 * FLT_MAX so that GetSize() remains finite
+		return AABox(Vec3::sReplicate(-0.5f * FLT_MAX), Vec3::sReplicate(0.5f * FLT_MAX));
 	}
 	}
 
 
 	/// Comparison operators
 	/// Comparison operators

+ 1 - 1
Jolt/Math/EigenValueSymmetric.h

@@ -30,7 +30,7 @@ bool EigenValueSymmetric(const Matrix &inMatrix, Matrix &outEigVec, Vector &outE
 {
 {
 	// This algorithm can generate infinite values, see comment below
 	// This algorithm can generate infinite values, see comment below
 	FPExceptionDisableInvalid disable_invalid;
 	FPExceptionDisableInvalid disable_invalid;
-	(void)disable_invalid;
+	JPH_UNUSED(disable_invalid);
 
 
 	// Maximum number of sweeps to make
 	// Maximum number of sweeps to make
 	const int cMaxSweeps = 50;
 	const int cMaxSweeps = 50;

+ 4 - 0
Jolt/Math/HalfFloat.h

@@ -5,6 +5,7 @@
 #pragma once
 #pragma once
 
 
 #include <Jolt/Math/Vec4.h>
 #include <Jolt/Math/Vec4.h>
+#include <Jolt/Core/FPException.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
@@ -132,6 +133,9 @@ template <int RoundingMode>
 JPH_INLINE HalfFloat FromFloat(float inV)
 JPH_INLINE HalfFloat FromFloat(float inV)
 {
 {
 #ifdef JPH_USE_F16C
 #ifdef JPH_USE_F16C
+	FPExceptionDisableOverflow disable_overflow;
+	JPH_UNUSED(disable_overflow);
+
 	union
 	union
 	{
 	{
 		__m128i		u128;
 		__m128i		u128;

+ 3 - 0
Jolt/Math/Math.h

@@ -9,6 +9,9 @@ JPH_NAMESPACE_BEGIN
 /// The constant \f$\pi\f$
 /// The constant \f$\pi\f$
 static constexpr float JPH_PI = 3.14159265358979323846f;
 static constexpr float JPH_PI = 3.14159265358979323846f;
 
 
+/// A large floating point value which, when squared, is still much smaller than FLT_MAX
+static constexpr float cLargeFloat = 1.0e15f;
+
 /// Convert a value from degrees to radians
 /// Convert a value from degrees to radians
 JPH_INLINE constexpr float DegreesToRadians(float inV)
 JPH_INLINE constexpr float DegreesToRadians(float inV)
 {
 {

+ 0 - 1
Jolt/Physics/Collision/BroadPhase/QuadTree.cpp

@@ -110,7 +110,6 @@ bool QuadTree::Node::EncapsulateChildBounds(int inChildIndex, const AABox &inBou
 // QuadTree
 // QuadTree
 ////////////////////////////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 
-const float QuadTree::cLargeFloat = 1.0e30f;
 const AABox QuadTree::cInvalidBounds(Vec3::sReplicate(cLargeFloat), Vec3::sReplicate(-cLargeFloat));
 const AABox QuadTree::cInvalidBounds(Vec3::sReplicate(cLargeFloat), Vec3::sReplicate(-cLargeFloat));
 
 
 void QuadTree::GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const
 void QuadTree::GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const

+ 0 - 1
Jolt/Physics/Collision/BroadPhase/QuadTree.h

@@ -262,7 +262,6 @@ public:
 private:
 private:
 	/// Constants
 	/// Constants
 	static const uint32			cInvalidNodeIndex = 0xffffffff;		///< Value used to indicate node index is invalid
 	static const uint32			cInvalidNodeIndex = 0xffffffff;		///< Value used to indicate node index is invalid
-	static const float			cLargeFloat;						///< A large floating point number that is small enough to not cause any overflows
 	static const AABox			cInvalidBounds;						///< Invalid bounding box using cLargeFloat
 	static const AABox			cInvalidBounds;						///< Invalid bounding box using cLargeFloat
 
 
 	/// We alternate between two trees in order to let collision queries complete in parallel to adding/removing objects to the tree
 	/// We alternate between two trees in order to let collision queries complete in parallel to adding/removing objects to the tree

+ 2 - 2
Jolt/Physics/Collision/Shape/HeightFieldShape.cpp

@@ -1869,8 +1869,8 @@ public:
 					uint32 stride = block_size_plus_1 - size_x_plus_1;
 					uint32 stride = block_size_plus_1 - size_x_plus_1;
 
 
 					// Start range with a very large inside-out box
 					// Start range with a very large inside-out box
-					Vec3 value_min = Vec3::sReplicate(1.0e30f);
-					Vec3 value_max = Vec3::sReplicate(-1.0e30f);
+					Vec3 value_min = Vec3::sReplicate(cLargeFloat);
+					Vec3 value_max = Vec3::sReplicate(-cLargeFloat);
 
 
 					// Loop over the samples to determine the min and max of this block
 					// Loop over the samples to determine the min and max of this block
 					for (uint32 block_y = 0; block_y < size_y_plus_1; ++block_y)
 					for (uint32 block_y = 0; block_y < size_y_plus_1; ++block_y)

+ 2 - 2
Jolt/Physics/Collision/Shape/HeightFieldShape.h

@@ -73,10 +73,10 @@ public:
 	uint32							mSampleCount = 0;
 	uint32							mSampleCount = 0;
 
 
 	/// Artificial minimal value of mHeightSamples, used for compression and can be used to update the terrain after creating with lower height values. If there are any lower values in mHeightSamples, this value will be ignored.
 	/// Artificial minimal value of mHeightSamples, used for compression and can be used to update the terrain after creating with lower height values. If there are any lower values in mHeightSamples, this value will be ignored.
-	float							mMinHeightValue = FLT_MAX;
+	float							mMinHeightValue = cLargeFloat;
 
 
 	/// Artificial maximum value of mHeightSamples, used for compression and can be used to update the terrain after creating with higher height values. If there are any higher values in mHeightSamples, this value will be ignored.
 	/// Artificial maximum value of mHeightSamples, used for compression and can be used to update the terrain after creating with higher height values. If there are any higher values in mHeightSamples, this value will be ignored.
-	float							mMaxHeightValue = -FLT_MAX;
+	float							mMaxHeightValue = -cLargeFloat;
 
 
 	/// When bigger than mMaterials.size() the internal material list will be preallocated to support this number of materials.
 	/// When bigger than mMaterials.size() the internal material list will be preallocated to support this number of materials.
 	/// This avoids reallocations when calling HeightFieldShape::SetMaterials with new materials later.
 	/// This avoids reallocations when calling HeightFieldShape::SetMaterials with new materials later.

+ 4 - 0
Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp

@@ -541,6 +541,10 @@ void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
 			if (group_idx[i] == -1)
 			if (group_idx[i] == -1)
 				bounds.Encapsulate(Vec3(mVertices[i].mPosition));
 				bounds.Encapsulate(Vec3(mVertices[i].mPosition));
 
 
+		// If the bounds are invalid, it means that there were no ungrouped vertices
+		if (!bounds.IsValid())
+			break;
+
 		// Determine longest and shortest axis
 		// Determine longest and shortest axis
 		Vec3 bounds_size = bounds.GetSize();
 		Vec3 bounds_size = bounds.GetSize();
 		uint max_axis = bounds_size.GetHighestComponentIndex();
 		uint max_axis = bounds_size.GetHighestComponentIndex();

+ 1 - 1
Jolt/Renderer/DebugRenderer.cpp

@@ -18,7 +18,7 @@ DebugRenderer *DebugRenderer::sInstance = nullptr;
 static const int sMaxLevel = 4;
 static const int sMaxLevel = 4;
 
 
 // Distance for each LOD level, these are tweaked for an object of approx. size 1. Use the lod scale to scale these distances.
 // Distance for each LOD level, these are tweaked for an object of approx. size 1. Use the lod scale to scale these distances.
-static const float sLODDistanceForLevel[] = { 5.0f, 10.0f, 40.0f, FLT_MAX };
+static const float sLODDistanceForLevel[] = { 5.0f, 10.0f, 40.0f, cLargeFloat };
 
 
 DebugRenderer::Triangle::Triangle(Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, ColorArg inColor)
 DebugRenderer::Triangle::Triangle(Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, ColorArg inColor)
 {
 {

+ 1 - 1
Jolt/Renderer/DebugRenderer.h

@@ -218,7 +218,7 @@ public:
 
 
 		/// Constructor
 		/// Constructor
 							Geometry(const AABox &inBounds) : mBounds(inBounds) { }
 							Geometry(const AABox &inBounds) : mBounds(inBounds) { }
-							Geometry(const Batch &inBatch, const AABox &inBounds) : mBounds(inBounds) { mLODs.push_back({ inBatch, FLT_MAX }); }
+							Geometry(const Batch &inBatch, const AABox &inBounds) : mBounds(inBounds) { mLODs.push_back({ inBatch, cLargeFloat }); }
 
 
 		/// Determine which LOD to render
 		/// Determine which LOD to render
 		/// @param inCameraPosition Current position of the camera
 		/// @param inCameraPosition Current position of the camera

+ 2 - 2
UnitTests/Geometry/GJKTests.cpp

@@ -51,7 +51,7 @@ TEST_SUITE("GJKTests")
 		{
 		{
 			// Test sphere s1 and s2, they should not collide, verify their closest points
 			// Test sphere s1 and s2, they should not collide, verify their closest points
 			Vec3 pa, pb, v = Vec3::sZero();
 			Vec3 pa, pb, v = Vec3::sZero();
-			float d = sqrt(gjk.GetClosestPoints(s1, s2, 1.0e-4f, FLT_MAX, v, pa, pb));
+			float d = sqrt(gjk.GetClosestPoints(s1, s2, 1.0e-4f, cLargeFloat, v, pa, pb));
 			CHECK_APPROX_EQUAL(c2.Length() - 2.0f, d, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2.Length() - 2.0f, d, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2.Normalized(), pa, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2.Normalized(), pa, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2 - c2.Normalized(), pb, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2 - c2.Normalized(), pb, 1.0e-4f);
@@ -60,7 +60,7 @@ TEST_SUITE("GJKTests")
 		{
 		{
 			// Test sphere s1 and s3, they should touch exactly, verify their closest points
 			// Test sphere s1 and s3, they should touch exactly, verify their closest points
 			Vec3 pa, pb, v = Vec3::sZero();
 			Vec3 pa, pb, v = Vec3::sZero();
-			float d = sqrt(gjk.GetClosestPoints(s1, s3, 1.0e-4f, FLT_MAX, v, pa, pb));
+			float d = sqrt(gjk.GetClosestPoints(s1, s3, 1.0e-4f, cLargeFloat, v, pa, pb));
 			CHECK_APPROX_EQUAL(0.0f, d, 1.0e-4f);
 			CHECK_APPROX_EQUAL(0.0f, d, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2.Normalized(), pa, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2.Normalized(), pa, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2.Normalized(), pb, 1.0e-4f);
 			CHECK_APPROX_EQUAL(c2.Normalized(), pb, 1.0e-4f);