Skip to content

Commit 0f85408

Browse files
committed
feat: Add cross-platform deterministic math via fdlibm
Replace hardware-dependent x87 FPU trig functions (fsin, fcos) in WWMath with fdlibm 5.3 — a portable, bit-exact IEEE 754 C implementation. This ensures lockstep CRC parity between macOS ARM64/x64 and Windows x86 clients, eliminating multiplayer desyncs caused by floating-point precision divergence. Changes: - Integrate fdlibm 5.3 via FetchContent from Okladnoj/fdlibm-deterministic - Replace all x87 asm blocks in wwmath.h with fdlibm wrappers - Route ~80+ direct sin/cos/sqrt/atan2 calls in GameLogic through WWMath - Replace Inv_Sqrt Quake-era hack with 1.0f/WWMath::Sqrt() - Gate all changes behind USE_DETERMINISTIC_MATH (RETAIL_COMPATIBLE_CRC) - Clean Weapon.cpp diff to contain only functional WWMath replacements - Preserve Fast_Sin/Fast_Cos LUT (already deterministic) - Leave render/UI layer (GameClient, WW3D2) on system math (no CRC impact) - Add SimulationMathCrc dual-path diagnostic (fdlibm vs system math)
1 parent 6266009 commit 0f85408

16 files changed

Lines changed: 402 additions & 286 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ endif()
6464

6565
include(cmake/config.cmake)
6666
include(cmake/gamespy.cmake)
67+
include(cmake/fdlibm.cmake)
6768
include(cmake/lzhl.cmake)
6869

6970
if (IS_VS6_BUILD)

Core/GameEngine/Include/Common/Diagnostic/SimulationMathCrc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ class SimulationMathCrc
2222
{
2323
public:
2424
static UnsignedInt calculate();
25+
static UnsignedInt calculateSystemMath();
2526
};

Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,35 +37,85 @@ static void appendSimulationMathCrc(XferCRC &xfer)
3737
0.9f, 1.0f, 2.1f, 1.2f);
3838

3939
factorsMatrix.Set(
40-
WWMath::Sin(0.7f) * log10f(2.3f),
41-
WWMath::Cos(1.1f) * powf(1.1f, 2.0f),
40+
WWMath::Sin(0.7f) * WWMath::Cos(2.3f),
41+
WWMath::Cos(1.1f) * WWMath::Sin(1.1f),
42+
WWMath::Tan(0.3f),
43+
WWMath::Asin(0.967302263f),
44+
WWMath::Acos(0.967302263f),
45+
WWMath::Atan(0.967302263f) * WWMath::Sqrt(1.21f),
46+
WWMath::Atan2(0.4f, 1.3f),
47+
WWMath::Sin(0.2f) * WWMath::Cos(0.2f),
48+
WWMath::Sqrt(0.4f) * WWMath::Tan(0.5f),
49+
WWMath::Sqrt(55788.84375f),
50+
WWMath::Acos(0.1f) * WWMath::Cos(2.3f),
51+
WWMath::Asin(0.4f));
52+
53+
Matrix3D::Multiply(matrix, factorsMatrix, &matrix);
54+
matrix.Get_Inverse(matrix);
55+
56+
xfer.xferMatrix3D(&matrix);
57+
}
58+
59+
UnsignedInt SimulationMathCrc::calculate()
60+
{
61+
XferCRC xfer;
62+
xfer.open("SimulationMathCrc");
63+
64+
setFPMode();
65+
66+
appendSimulationMathCrc(xfer);
67+
68+
#ifdef _WIN32
69+
_fpreset();
70+
#endif
71+
72+
xfer.close();
73+
74+
return xfer.getCRC();
75+
}
76+
77+
static void appendSimulationMathCrcSystemMath(XferCRC &xfer)
78+
{
79+
Matrix3D matrix;
80+
Matrix3D factorsMatrix;
81+
82+
matrix.Set(
83+
4.1f, 1.2f, 0.3f, 0.4f,
84+
0.5f, 3.6f, 0.7f, 0.8f,
85+
0.9f, 1.0f, 2.1f, 1.2f);
86+
87+
factorsMatrix.Set(
88+
sinf(0.7f) * cosf(2.3f),
89+
cosf(1.1f) * sinf(1.1f),
4290
tanf(0.3f),
4391
asinf(0.967302263f),
4492
acosf(0.967302263f),
45-
atanf(0.967302263f) * powf(1.1f, 2.0f),
93+
atanf(0.967302263f) * sqrtf(1.21f),
4694
atan2f(0.4f, 1.3f),
47-
sinhf(0.2f),
48-
coshf(0.4f) * tanhf(0.5f),
95+
sinf(0.2f) * cosf(0.2f),
96+
sqrtf(0.4f) * tanf(0.5f),
4997
sqrtf(55788.84375f),
50-
expf(0.1f) * log10f(2.3f),
51-
logf(1.4f));
98+
acosf(0.1f) * cosf(2.3f),
99+
asinf(0.4f));
52100

53101
Matrix3D::Multiply(matrix, factorsMatrix, &matrix);
54102
matrix.Get_Inverse(matrix);
55103

56104
xfer.xferMatrix3D(&matrix);
57105
}
58106

59-
UnsignedInt SimulationMathCrc::calculate()
107+
UnsignedInt SimulationMathCrc::calculateSystemMath()
60108
{
61109
XferCRC xfer;
62-
xfer.open("SimulationMathCrc");
110+
xfer.open("SimulationMathCrc_SystemMath");
63111

64112
setFPMode();
65113

66-
appendSimulationMathCrc(xfer);
114+
appendSimulationMathCrcSystemMath(xfer);
67115

116+
#ifdef _WIN32
68117
_fpreset();
118+
#endif
69119

70120
xfer.close();
71121

Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ target_link_libraries(core_wwmath PRIVATE
8989
core_wwcommon
9090
corei_always
9191
core_wwsaveload
92+
fdlibm
9293
)
9394

9495
# @todo Test its impact and see what to do with the legacy functions.

Core/Libraries/Source/WWVegas/WWMath/wwmath.h

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@
3939
#include "always.h"
4040
#include <math.h>
4141
#include <float.h>
42+
#include "fdlibm_det.h"
4243
#include <assert.h>
4344

45+
#ifdef RETAIL_COMPATIBLE_CRC
46+
#define USE_DETERMINISTIC_MATH
47+
#endif
48+
4449
/*
4550
** Some global constants.
4651
*/
@@ -107,19 +112,11 @@ class WWMath
107112
static WWINLINE int Float_To_Int_Chop(const float& f);
108113
static WWINLINE int Float_To_Int_Floor(const float& f);
109114

110-
#if defined(_MSC_VER) && defined(_M_IX86)
111-
static WWINLINE float Cos(float val);
112-
static WWINLINE float Sin(float val);
113-
static WWINLINE float Sqrt(float val);
114-
static WWINLINE float Inv_Sqrt(float a); // Some 30% faster inverse square root than regular C++ compiled, from Intel's math library
115-
static WWINLINE long Float_To_Long(float f);
116-
#else
117115
static WWINLINE float Cos(float val);
118116
static WWINLINE float Sin(float val);
119117
static WWINLINE float Sqrt(float val);
120118
static WWINLINE float Inv_Sqrt(float a);
121119
static WWINLINE long Float_To_Long(float f);
122-
#endif
123120

124121

125122
static WWINLINE float Fast_Sin(float val);
@@ -133,8 +130,29 @@ class WWMath
133130
static WWINLINE float Asin(float val);
134131

135132

133+
#ifdef USE_DETERMINISTIC_MATH
134+
static WWINLINE float Atan(float x) { return static_cast<float>(fdlibm_atan(x)); }
135+
static WWINLINE float Atan2(float y, float x) { return static_cast<float>(fdlibm_atan2(y, x)); }
136+
static WWINLINE float Tan(float x) { return static_cast<float>(fdlibm_tan(x)); }
137+
static WWINLINE float Sinh(float x) { return static_cast<float>(fdlibm_sinh(x)); }
138+
static WWINLINE float Cosh(float x) { return static_cast<float>(fdlibm_cosh(x)); }
139+
static WWINLINE float Tanh(float x) { return static_cast<float>(fdlibm_tanh(x)); }
140+
static WWINLINE float Exp(float x) { return static_cast<float>(fdlibm_exp(x)); }
141+
static WWINLINE float Log(float x) { return static_cast<float>(fdlibm_log(x)); }
142+
static WWINLINE float Log10(float x) { return static_cast<float>(fdlibm_log10(x)); }
143+
static WWINLINE float Pow(float x, float y) { return static_cast<float>(fdlibm_pow(x, y)); }
144+
#else
136145
static WWINLINE float Atan(float x) { return static_cast<float>(atan(x)); }
137146
static WWINLINE float Atan2(float y, float x) { return static_cast<float>(atan2(y, x)); }
147+
static WWINLINE float Tan(float x) { return static_cast<float>(tan(x)); }
148+
static WWINLINE float Sinh(float x) { return static_cast<float>(sinh(x)); }
149+
static WWINLINE float Cosh(float x) { return static_cast<float>(cosh(x)); }
150+
static WWINLINE float Tanh(float x) { return static_cast<float>(tanh(x)); }
151+
static WWINLINE float Exp(float x) { return static_cast<float>(exp(x)); }
152+
static WWINLINE float Log(float x) { return static_cast<float>(log(x)); }
153+
static WWINLINE float Log10(float x) { return static_cast<float>(log10(x)); }
154+
static WWINLINE float Pow(float x, float y) { return static_cast<float>(pow(x, y)); }
155+
#endif
138156
static WWINLINE float Sign(float val);
139157
static WWINLINE float Ceil(float val) { return ceilf(val); }
140158
static WWINLINE float Floor(float val) { return floorf(val); }
@@ -313,41 +331,26 @@ WWINLINE bool WWMath::Is_Valid_Double(double x)
313331
// Float to long
314332
// ----------------------------------------------------------------------------
315333

316-
#if defined(_MSC_VER) && defined(_M_IX86)
317334
WWINLINE long WWMath::Float_To_Long(float f)
318335
{
319-
long i;
320-
321-
__asm {
322-
fld[f]
323-
fistp[i]
324-
}
325-
326-
return i;
336+
return (long)(f + (f >= 0.0f ? 0.5f : -0.5f));
327337
}
328-
#else
329-
WWINLINE long WWMath::Float_To_Long(float f)
330-
{
331-
return (long)f;
332-
}
333-
#endif
334338

335339
WWINLINE long WWMath::Float_To_Long(double f)
336340
{
337-
#if defined(_MSC_VER) && defined(_M_IX86)
338-
long retval;
339-
__asm fld qword ptr[f]
340-
__asm fistp dword ptr[retval]
341-
return retval;
342-
#else
343-
return (long)f;
344-
#endif
341+
return (long)(f + (f >= 0.0 ? 0.5 : -0.5));
345342
}
346343

347344
// ----------------------------------------------------------------------------
348-
// Cos
345+
// Cos (deterministic, fdlibm)
349346
// ----------------------------------------------------------------------------
350347

348+
#ifdef USE_DETERMINISTIC_MATH
349+
WWINLINE float WWMath::Cos(float val)
350+
{
351+
return (float)fdlibm_cos((double)val);
352+
}
353+
#else
351354
#if defined(_MSC_VER) && defined(_M_IX86)
352355
WWINLINE float WWMath::Cos(float val)
353356
{
@@ -365,11 +368,18 @@ WWINLINE float WWMath::Cos(float val)
365368
return cosf(val);
366369
}
367370
#endif
371+
#endif
368372

369373
// ----------------------------------------------------------------------------
370-
// Sin
374+
// Sin (deterministic, fdlibm)
371375
// ----------------------------------------------------------------------------
372376

377+
#ifdef USE_DETERMINISTIC_MATH
378+
WWINLINE float WWMath::Sin(float val)
379+
{
380+
return (float)fdlibm_sin((double)val);
381+
}
382+
#else
373383
#if defined(_MSC_VER) && defined(_M_IX86)
374384
WWINLINE float WWMath::Sin(float val)
375385
{
@@ -387,6 +397,7 @@ WWINLINE float WWMath::Sin(float val)
387397
return sinf(val);
388398
}
389399
#endif
400+
#endif
390401

391402
// ----------------------------------------------------------------------------
392403
// Fast, table based sin
@@ -516,10 +527,17 @@ WWINLINE float WWMath::Fast_Acos(float val)
516527
// Arc cos
517528
// ----------------------------------------------------------------------------
518529

530+
#ifdef USE_DETERMINISTIC_MATH
531+
WWINLINE float WWMath::Acos(float val)
532+
{
533+
return (float)fdlibm_acos((double)val);
534+
}
535+
#else
519536
WWINLINE float WWMath::Acos(float val)
520537
{
521538
return (float)acos(val);
522539
}
540+
#endif
523541

524542
// ----------------------------------------------------------------------------
525543
// Fast, table based arc sin
@@ -553,15 +571,28 @@ WWINLINE float WWMath::Fast_Asin(float val)
553571
// Arc sin
554572
// ----------------------------------------------------------------------------
555573

574+
#ifdef USE_DETERMINISTIC_MATH
575+
WWINLINE float WWMath::Asin(float val)
576+
{
577+
return (float)fdlibm_asin((double)val);
578+
}
579+
#else
556580
WWINLINE float WWMath::Asin(float val)
557581
{
558582
return (float)asin(val);
559583
}
584+
#endif
560585

561586
// ----------------------------------------------------------------------------
562-
// Sqrt
587+
// Sqrt (IEEE 754 guarantees correctly-rounded results on all platforms)
563588
// ----------------------------------------------------------------------------
564589

590+
#ifdef USE_DETERMINISTIC_MATH
591+
WWINLINE float WWMath::Sqrt(float val)
592+
{
593+
return (float)sqrt((double)val);
594+
}
595+
#else
565596
#if defined(_MSC_VER) && defined(_M_IX86)
566597
WWINLINE float WWMath::Sqrt(float val)
567598
{
@@ -579,6 +610,7 @@ WWINLINE float WWMath::Sqrt(float val)
579610
return (float)sqrt(val);
580611
}
581612
#endif
613+
#endif
582614

583615
WWINLINE int WWMath::Float_To_Int_Chop(const float& f)
584616
{
@@ -606,10 +638,16 @@ WWINLINE int WWMath::Float_To_Int_Floor(const float& f)
606638
return r;
607639
}
608640

609-
// ----------------------------------------------------------------------------
610-
// Inverse square root
641+
/// ----------------------------------------------------------------------------
642+
// Inverse square root (deterministic, portable Quake III style + fdlibm)
611643
// ----------------------------------------------------------------------------
612644

645+
#ifdef USE_DETERMINISTIC_MATH
646+
WWINLINE float WWMath::Inv_Sqrt(float number)
647+
{
648+
return 1.0f / WWMath::Sqrt(number);
649+
}
650+
#else
613651
#if defined(_MSC_VER) && defined(_M_IX86)
614652
WWINLINE float WWMath::Inv_Sqrt(float a)
615653
{
@@ -667,6 +705,7 @@ WWINLINE float WWMath::Inv_Sqrt(float val)
667705
return 1.0f / (float)sqrt(val);
668706
}
669707
#endif
708+
#endif
670709

671710
WWINLINE float WWMath::Normalize_Angle(float angle)
672711
{

GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
3232
#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
3333

34+
#include "WWMath/wwmath.h"
35+
3436
#include "Common/BuildAssistant.h"
3537
#include "Common/GlobalData.h"
3638
#include "Common/Player.h"
@@ -806,8 +808,8 @@ LegalBuildCode BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos
806808
if (myFactoryExitWidth>0) {
807809
myExitPos = *worldPos;
808810
checkMyExit = true;
809-
Real c = (Real)cos(angle);
810-
Real s = (Real)sin(angle);
811+
Real c = WWMath::Cos(angle);
812+
Real s = WWMath::Sin(angle);
811813
Real offset = build->getTemplateGeometryInfo().getMajorRadius() + myFactoryExitWidth/2.0f;
812814
myExitPos.x += c*offset;
813815
myExitPos.y += s*offset;
@@ -854,8 +856,8 @@ LegalBuildCode BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos
854856
if (themFactoryExitWidth>0) {
855857
hisExitPos = *them->getPosition();
856858
checkHisExit = true;
857-
Real c = (Real)cos(them->getOrientation());
858-
Real s = (Real)sin(them->getOrientation());
859+
Real c = WWMath::Cos(them->getOrientation());
860+
Real s = WWMath::Sin(them->getOrientation());
859861
Real offset = them->getGeometryInfo().getMajorRadius() + themFactoryExitWidth/2.0f;
860862
hisExitPos.x += c*offset;
861863
hisExitPos.y += s*offset;
@@ -1435,7 +1437,7 @@ Bool BuildAssistant::moveObjectsForConstruction( const ThingTemplate *whatToBuil
14351437
Bool anyUnmovables = false;
14361438
MemoryPoolObjectHolder hold( iter );
14371439

1438-
Real radius = sqrt(pow(gi.getMajorRadius(), 2) + pow(gi.getMinorRadius(), 2));
1440+
Real radius = WWMath::Sqrt(gi.getMajorRadius() * gi.getMajorRadius() + gi.getMinorRadius() * gi.getMinorRadius());
14391441
radius *= 1.4f; // Fudge the distance,
14401442

14411443
for( Object *them = iter->first(); them; them = iter->next() )

0 commit comments

Comments
 (0)