Skip to content

Commit 8f7952d

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 8f7952d

16 files changed

Lines changed: 388 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: 61 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,15 @@ 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+
#else
136138
static WWINLINE float Atan(float x) { return static_cast<float>(atan(x)); }
137139
static WWINLINE float Atan2(float y, float x) { return static_cast<float>(atan2(y, x)); }
140+
static WWINLINE float Tan(float x) { return static_cast<float>(tan(x)); }
141+
#endif
138142
static WWINLINE float Sign(float val);
139143
static WWINLINE float Ceil(float val) { return ceilf(val); }
140144
static WWINLINE float Floor(float val) { return floorf(val); }
@@ -313,41 +317,26 @@ WWINLINE bool WWMath::Is_Valid_Double(double x)
313317
// Float to long
314318
// ----------------------------------------------------------------------------
315319

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

335325
WWINLINE long WWMath::Float_To_Long(double f)
336326
{
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
327+
return (long)(f + (f >= 0.0 ? 0.5 : -0.5));
345328
}
346329

347330
// ----------------------------------------------------------------------------
348-
// Cos
331+
// Cos (deterministic, fdlibm)
349332
// ----------------------------------------------------------------------------
350333

334+
#ifdef USE_DETERMINISTIC_MATH
335+
WWINLINE float WWMath::Cos(float val)
336+
{
337+
return (float)fdlibm_cos((double)val);
338+
}
339+
#else
351340
#if defined(_MSC_VER) && defined(_M_IX86)
352341
WWINLINE float WWMath::Cos(float val)
353342
{
@@ -365,11 +354,18 @@ WWINLINE float WWMath::Cos(float val)
365354
return cosf(val);
366355
}
367356
#endif
357+
#endif
368358

369359
// ----------------------------------------------------------------------------
370-
// Sin
360+
// Sin (deterministic, fdlibm)
371361
// ----------------------------------------------------------------------------
372362

363+
#ifdef USE_DETERMINISTIC_MATH
364+
WWINLINE float WWMath::Sin(float val)
365+
{
366+
return (float)fdlibm_sin((double)val);
367+
}
368+
#else
373369
#if defined(_MSC_VER) && defined(_M_IX86)
374370
WWINLINE float WWMath::Sin(float val)
375371
{
@@ -387,6 +383,7 @@ WWINLINE float WWMath::Sin(float val)
387383
return sinf(val);
388384
}
389385
#endif
386+
#endif
390387

391388
// ----------------------------------------------------------------------------
392389
// Fast, table based sin
@@ -516,10 +513,17 @@ WWINLINE float WWMath::Fast_Acos(float val)
516513
// Arc cos
517514
// ----------------------------------------------------------------------------
518515

516+
#ifdef USE_DETERMINISTIC_MATH
517+
WWINLINE float WWMath::Acos(float val)
518+
{
519+
return (float)fdlibm_acos((double)val);
520+
}
521+
#else
519522
WWINLINE float WWMath::Acos(float val)
520523
{
521524
return (float)acos(val);
522525
}
526+
#endif
523527

524528
// ----------------------------------------------------------------------------
525529
// Fast, table based arc sin
@@ -553,15 +557,28 @@ WWINLINE float WWMath::Fast_Asin(float val)
553557
// Arc sin
554558
// ----------------------------------------------------------------------------
555559

560+
#ifdef USE_DETERMINISTIC_MATH
561+
WWINLINE float WWMath::Asin(float val)
562+
{
563+
return (float)fdlibm_asin((double)val);
564+
}
565+
#else
556566
WWINLINE float WWMath::Asin(float val)
557567
{
558568
return (float)asin(val);
559569
}
570+
#endif
560571

561572
// ----------------------------------------------------------------------------
562-
// Sqrt
573+
// Sqrt (IEEE 754 guarantees correctly-rounded results on all platforms)
563574
// ----------------------------------------------------------------------------
564575

576+
#ifdef USE_DETERMINISTIC_MATH
577+
WWINLINE float WWMath::Sqrt(float val)
578+
{
579+
return (float)sqrt((double)val);
580+
}
581+
#else
565582
#if defined(_MSC_VER) && defined(_M_IX86)
566583
WWINLINE float WWMath::Sqrt(float val)
567584
{
@@ -579,6 +596,7 @@ WWINLINE float WWMath::Sqrt(float val)
579596
return (float)sqrt(val);
580597
}
581598
#endif
599+
#endif
582600

583601
WWINLINE int WWMath::Float_To_Int_Chop(const float& f)
584602
{
@@ -606,10 +624,16 @@ WWINLINE int WWMath::Float_To_Int_Floor(const float& f)
606624
return r;
607625
}
608626

609-
// ----------------------------------------------------------------------------
610-
// Inverse square root
627+
/// ----------------------------------------------------------------------------
628+
// Inverse square root (deterministic, portable Quake III style + fdlibm)
611629
// ----------------------------------------------------------------------------
612630

631+
#ifdef USE_DETERMINISTIC_MATH
632+
WWINLINE float WWMath::Inv_Sqrt(float number)
633+
{
634+
return 1.0f / WWMath::Sqrt(number);
635+
}
636+
#else
613637
#if defined(_MSC_VER) && defined(_M_IX86)
614638
WWINLINE float WWMath::Inv_Sqrt(float a)
615639
{
@@ -667,6 +691,7 @@ WWINLINE float WWMath::Inv_Sqrt(float val)
667691
return 1.0f / (float)sqrt(val);
668692
}
669693
#endif
694+
#endif
670695

671696
WWINLINE float WWMath::Normalize_Angle(float angle)
672697
{

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)