From 7be51db0f1853f2a9c4c403f4552826a3dcc8544 Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 22 Apr 2008 16:24:39 +0000 Subject: SSE floating-point-exception (FPE) fix. Occured when playing ogg-files or with projectM. See UCommon.pas for more information. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1034 b956fd51-792f-4845-bead-9b4dfca2ff2c --- Game/Code/Classes/UCommon.pas | 125 ++++++++++++++++++++++++++++++++++++++++-- Game/Code/Classes/UMain.pas | 8 ++- 2 files changed, 128 insertions(+), 5 deletions(-) (limited to 'Game') diff --git a/Game/Code/Classes/UCommon.pas b/Game/Code/Classes/UCommon.pas index 5582c59b..7539a958 100644 --- a/Game/Code/Classes/UCommon.pas +++ b/Game/Code/Classes/UCommon.pas @@ -11,10 +11,10 @@ interface uses SysUtils, Classes, -{$IFDEF MSWINDOWS} + {$IFDEF MSWINDOWS} Windows, Messages, -{$ENDIF} + {$ENDIF} ULog; {$IFNDEF DARWIN} @@ -42,6 +42,9 @@ procedure DeallocateHWnd(hWnd: HWND); function StringReplaceW(text : WideString; search, rep: WideChar):WideString; function AdaptFilePaths( const aPath : widestring ): widestring; +procedure DisableFloatingPointExceptions(); +procedure SetDefaultNumericLocale(); +procedure RestoreNumericLocale(); {$IFNDEF win32} procedure ZeroMemory( Destination: Pointer; Length: DWORD ); @@ -63,12 +66,126 @@ function IsControlChar(ch: WideChar): boolean; implementation uses -{$IFDEF Delphi} + Math, + {$IFDEF Delphi} Dialogs, -{$ENDIF} + {$ENDIF} + {$IFDEF LINUX} + libc, + {$ENDIF} UMain, UConfig; +var + PrevNumLocale: string; + +// In Linux and maybe MacOSX some units (like cwstring) call setlocale(LC_ALL, '') +// to set the language/country specific locale (e.g. charset) for this application. +// Unfortunately, LC_NUMERIC is set by this call too. +// It defines the decimal-separator and other country-specific numeric settings. +// This parameter is used by the C string-to-float parsing functions atof() and strtod(). +// After changing LC_NUMERIC some external C-based libs (like projectM) are not +// able to parse strings correctly +// (e.g. in Germany "0.9" is not recognized as a valid number anymore but "0,9" is). +// So we reset the numeric settings to the default ('C'). +// Note: The behaviour of Pascal parsing functions (e.g. strtofloat()) is not +// changed by this because it doesn't use the locale-settings. +// TODO: +// - Check if this is needed in MacOSX (at least the locale is set in cwstring) +// - Find out which libs are concerned by this problem. +// If only projectM is concerned by this problem set and restore the numeric locale +// for each call to projectM instead of changing it globally. +procedure SetDefaultNumericLocale(); +begin + {$ifdef LINUX} + PrevNumLocale := setlocale(LC_NUMERIC, nil); + setlocale(LC_NUMERIC, 'C'); + {$endif} +end; + +procedure RestoreNumericLocale(); +begin + {$ifdef LINUX} + setlocale(LC_NUMERIC, PChar(PrevNumLocale)); + {$endif} +end; + +(* + * If an invalid floating point operation was performed the Floating-point unit (FPU) + * generates a Floating-point exception (FPE). Dependending on the settings in + * the FPU's control-register (interrupt mask) the FPE is handled by the FPU itself + * (we will call this as "FPE disabled" later on) or is passed to the application + * (FPE enabled). + * If FPEs are enabled a floating-point division by zero (e.g. 10.0 / 0.0) is + * considered an error and an exception is thrown. Otherwise the FPU will handle + * the error and return the result infinity (INF) (10.0 / 0.0 = INF) without + * throwing an error to the application. + * The same applies to a division by INF that either raises an exception + * (FPE enabled) or returns 0.0 (FPE disabled). + * Normally (as with C-programs), Floating-point exceptions (FPE) are DISABLED + * on program startup (at least with Intel CPUs), but for some strange reasons + * they are ENABLED in pascal (both delphi and FPC) by default. + * Many libs operating with floating-point values rely heavily on the C-specific + * behaviour. So using them in delphi is a ticking time-bomb because sooner or + * later they will crash because of an FPE (this problem occurs massively + * in OpenGL-based libs like projectM). In contrast to this no error will occur + * if the lib is linked to a C-program. + * + * Further info on FPUs: + * For x86 and x86_64 CPUs we have to consider two FPU instruction sets. + * The math co-processor i387 (aka 8087 or x87) set introduced with the i386 + * and SSE (Streaming SIMD Extensions) introduced with the Pentium3. + * Both of them have separate control-registers (x87: FPUControlWord, SSE: MXCSR) + * to control FPEs. Either has (among others) 6bits to enable/disable several + * exception types (Invalid,Denormalized,Zero,Overflow,Underflow,Precision). + * Those exception-types must all be masked (=1) to get the default C behaviour. + * The control-registers can be set with the asm-ops FLDCW (x87) and LDMXCSR (SSE). + * Instead of using assembler code, we can use Set8087CW() provided by delphi and + * FPC to set the x87 control-word. FPC also provides SetSSECSR() for SSE's MXCSR. + * Note that both Delphi and FPC enable FPEs (e.g. for div-by-zero) on program + * startup but only FPC enables FPEs (especially div-by-zero) for SSE too. + * So we have to mask FPEs for x87 in Delphi and FPC and for SSE in FPC only. + * FPC and Delphi both provide a SetExceptionMask() for control of the FPE + * mask. SetExceptionMask() sets the masks for x87 in Delphi and for x87 and SSE + * in FPC (seems as if Delphi [2005] is not SSE aware). So SetExceptionMask() + * is what we need and it even is plattform and CPU independent. + * + * Pascal OpenGL headers (like the Delphi standard ones or JEDI-SDL headers) + * already call Set8087CW() to disable FPEs but due to some bugs in the JEDI-SDL + * headers they do not work properly with FPC. I already patched them, so they + * work at least until they are updated the next time. In addition Set8086CW() + * does not suffice to disable FPEs because the SSE FPEs are not disabled by this. + * FPEs with SSE are a big problem with some libs because many linux distributions + * optimize code for SSE or Pentium3 (for example: int(INF) which convert the + * double value "infinity" to an integer might be automatically optimized by + * using SSE's CVTSD2SI instruction). So SSE FPEs must be turned off in any case + * to make USDX portable. + * + * Summary: + * Call this function on initialization to make sure FPEs are turned off. + * It will solve a lot of errors with FPEs in external libs. + *) +procedure DisableFloatingPointExceptions(); +begin + (* + // We will use SetExceptionMask() instead of Set8087CW()/SetSSECSR(). + // Note: Leave these lines for documentation purposes just in case + // SetExceptionMask() does not work anymore (due to bugs in FPC etc.). + {$IF Defined(CPU386) or Defined(CPUI386) or Defined(CPUX86_64)} + Set8087CW($133F); + {$IFEND} + {$IF Defined(FPC)} + if (has_sse_support) then + SetSSECSR($1F80); + {$IFEND} + *) + + // disable all of the six FPEs (x87 and SSE) to be compatible with C/C++ and + // other libs which rely on the standard FPU behaviour (no div-by-zero FPE anymore). + SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, + exOverflow, exUnderflow, exPrecision]); +end; + function StringReplaceW(text : WideString; search, rep: WideChar):WideString; var iPos : integer; diff --git a/Game/Code/Classes/UMain.pas b/Game/Code/Classes/UMain.pas index fef2ff31..2e49ac8d 100644 --- a/Game/Code/Classes/UMain.pas +++ b/Game/Code/Classes/UMain.pas @@ -131,6 +131,7 @@ uses UParty, UConfig, UCore, + UCommon, UGraphicClasses, UPluginDefs, UPlatform; @@ -148,6 +149,11 @@ begin if Platform.TerminateIfAlreadyRunning( {var} WndTitle) then Exit; + // fix floating-point exceptions (FPE) + DisableFloatingPointExceptions(); + // fix the locale for string-to-float parsing in C-libs + SetDefaultNumericLocale(); + //------------------------------ //StartUp - Create Classes and Load Files //------------------------------ @@ -177,7 +183,7 @@ begin // SDL Log.BenchmarkStart(1); Log.LogStatus('Initialize SDL', 'Initialization'); - SDL_Init(SDL_INIT_VIDEO); + SDL_Init(SDL_INIT_VIDEO or SDL_INIT_TIMER); Log.BenchmarkEnd(1); Log.LogBenchmark('Initializing SDL', 1); -- cgit v1.2.3