mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-30 16:11:11 +00:00
f81d32918f
Detect CPU features at runtime via cpuid - code borrowed from libavutil. Availability of cpuid is not checked since pentium3 supports it and that was the minimum requirement anyway. Only features enabled at compile time will be available. Forced MMX/SSE/SSE2/SSE3 and it passed all tests via: ./doom3.x86_64 +disconnect +set s_noSound 1 +testSIMD
664 lines
15 KiB
C++
664 lines
15 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
#include "../../idlib/precompiled.h"
|
|
#include "../posix/posix_public.h"
|
|
#include "../sys_local.h"
|
|
#include "local.h"
|
|
|
|
#include <pthread.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
|
|
#ifdef ID_MCHECK
|
|
#include <mcheck.h>
|
|
#endif
|
|
|
|
static idStr basepath;
|
|
static idStr savepath;
|
|
|
|
/*
|
|
===========
|
|
Sys_InitScanTable
|
|
===========
|
|
*/
|
|
void Sys_InitScanTable( void ) {
|
|
common->DPrintf( "TODO: Sys_InitScanTable\n" );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Sys_AsyncThread
|
|
=================
|
|
*/
|
|
THREAD_RETURN_TYPE Sys_AsyncThread( void * ) {
|
|
int now;
|
|
int next;
|
|
int want_sleep;
|
|
|
|
// multi tick compensate for poor schedulers (Linux 2.4)
|
|
int ticked, to_ticked;
|
|
now = Sys_Milliseconds();
|
|
ticked = now >> 4;
|
|
while (1) {
|
|
// sleep
|
|
now = Sys_Milliseconds();
|
|
next = ( now & 0xFFFFFFF0 ) + 0x10;
|
|
want_sleep = ( next-now-1 ) * 1000;
|
|
if ( want_sleep > 0 ) {
|
|
usleep( want_sleep ); // sleep 1ms less than true target
|
|
}
|
|
|
|
// compensate if we slept too long
|
|
now = Sys_Milliseconds();
|
|
to_ticked = now >> 4;
|
|
|
|
// show ticking statistics - every 100 ticks, print a summary
|
|
#if 0
|
|
#define STAT_BUF 100
|
|
static int stats[STAT_BUF];
|
|
static int counter = 0;
|
|
// how many ticks to play
|
|
stats[counter] = to_ticked - ticked;
|
|
counter++;
|
|
if (counter == STAT_BUF) {
|
|
Sys_DebugPrintf("\n");
|
|
for( int i = 0; i < STAT_BUF; i++) {
|
|
if ( ! (i & 0xf) ) {
|
|
Sys_DebugPrintf("\n");
|
|
}
|
|
Sys_DebugPrintf( "%d ", stats[i] );
|
|
}
|
|
Sys_DebugPrintf("\n");
|
|
counter = 0;
|
|
}
|
|
#endif
|
|
|
|
while ( ticked < to_ticked ) {
|
|
common->Async();
|
|
ticked++;
|
|
Sys_TriggerEvent( TRIGGER_EVENT_ONE );
|
|
}
|
|
// thread exit
|
|
pthread_testcancel();
|
|
}
|
|
|
|
return (THREAD_RETURN_TYPE) 0;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
Sys_DefaultSavePath
|
|
==============
|
|
*/
|
|
const char *Sys_DefaultSavePath(void) {
|
|
#if defined( ID_DEMO_BUILD )
|
|
sprintf( savepath, "%s/.doom3-demo", getenv( "HOME" ) );
|
|
#else
|
|
sprintf( savepath, "%s/.doom3", getenv( "HOME" ) );
|
|
#endif
|
|
return savepath.c_str();
|
|
}
|
|
/*
|
|
==============
|
|
Sys_EXEPath
|
|
==============
|
|
*/
|
|
const char *Sys_EXEPath( void ) {
|
|
static char buf[ 1024 ];
|
|
idStr linkpath;
|
|
int len;
|
|
|
|
buf[ 0 ] = '\0';
|
|
sprintf( linkpath, "/proc/%d/exe", getpid() );
|
|
len = readlink( linkpath.c_str(), buf, sizeof( buf ) );
|
|
if ( len == -1 ) {
|
|
Sys_Printf("couldn't stat exe path link %s\n", linkpath.c_str());
|
|
buf[ 0 ] = '\0';
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Sys_DefaultBasePath
|
|
|
|
Get the default base path
|
|
- binary image path
|
|
- current directory
|
|
- hardcoded
|
|
Try to be intelligent: if there is no BASE_GAMEDIR, try the next path
|
|
================
|
|
*/
|
|
const char *Sys_DefaultBasePath(void) {
|
|
struct stat st;
|
|
idStr testbase;
|
|
basepath = Sys_EXEPath();
|
|
if ( basepath.Length() ) {
|
|
basepath.StripFilename();
|
|
testbase = basepath; testbase += "/"; testbase += BASE_GAMEDIR;
|
|
if ( stat( testbase.c_str(), &st ) != -1 && S_ISDIR( st.st_mode ) ) {
|
|
return basepath.c_str();
|
|
} else {
|
|
common->Printf( "no '%s' directory in exe path %s, skipping\n", BASE_GAMEDIR, basepath.c_str() );
|
|
}
|
|
}
|
|
if ( basepath != Posix_Cwd() ) {
|
|
basepath = Posix_Cwd();
|
|
testbase = basepath; testbase += "/"; testbase += BASE_GAMEDIR;
|
|
if ( stat( testbase.c_str(), &st ) != -1 && S_ISDIR( st.st_mode ) ) {
|
|
return basepath.c_str();
|
|
} else {
|
|
common->Printf("no '%s' directory in cwd path %s, skipping\n", BASE_GAMEDIR, basepath.c_str());
|
|
}
|
|
}
|
|
common->Printf( "WARNING: using hardcoded default base path\n" );
|
|
return LINUX_DEFAULT_PATH;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Sys_GetConsoleKey
|
|
===============
|
|
*/
|
|
unsigned char Sys_GetConsoleKey( bool shifted ) {
|
|
return shifted ? '~' : '`';
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Sys_Shutdown
|
|
===============
|
|
*/
|
|
void Sys_Shutdown( void ) {
|
|
basepath.Clear();
|
|
savepath.Clear();
|
|
Posix_Shutdown();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Sys_GetProcessorId
|
|
===============
|
|
*/
|
|
static char cpustring[13] = "generic\0";
|
|
|
|
#if defined(__x86_64__) || defined(__i386__)
|
|
#if __x86_64__
|
|
# define REG_b "rbx"
|
|
# define REG_S "rsi"
|
|
#elif __i386__
|
|
# define REG_b "ebx"
|
|
# define REG_S "esi"
|
|
#endif
|
|
|
|
#define cpuid(index,eax,ebx,ecx,edx) \
|
|
__asm__ volatile \
|
|
( "mov %%" REG_b ", %%" REG_S "\n\t" \
|
|
"cpuid\n\t" \
|
|
"xchg %%" REG_b ", %%" REG_S \
|
|
: "=a" (eax), "=S" (ebx), \
|
|
"=c" (ecx), "=d" (edx) \
|
|
: "0" (index));
|
|
|
|
int Sys_GetProcessorId( void ) {
|
|
int eax, ebx, ecx, edx;
|
|
int max_std_level, max_ext_level, std_caps=0, ext_caps=0;
|
|
union { int i[3]; char c[12]; } vendor;
|
|
|
|
int i = CPUID_GENERIC;
|
|
|
|
cpuid(0, max_std_level, ebx, ecx, edx);
|
|
vendor.i[0] = ebx;
|
|
vendor.i[1] = edx;
|
|
vendor.i[2] = ecx;
|
|
|
|
strncpy(cpustring, vendor.c, 12);
|
|
cpustring[12] = 0;
|
|
|
|
Sys_Printf("Detected '%s' CPU with", cpustring);
|
|
|
|
if (max_std_level >= 1) {
|
|
cpuid(1, eax, ebx, ecx, std_caps);
|
|
|
|
#ifdef __MMX__
|
|
if (std_caps & (1<<23)) {
|
|
Sys_Printf(" MMX");
|
|
i |= CPUID_MMX;
|
|
}
|
|
#endif
|
|
#ifdef __SSE__
|
|
if (std_caps & (1<<25)) {
|
|
Sys_Printf(" SSE");
|
|
i |= CPUID_SSE;
|
|
}
|
|
#endif
|
|
#ifdef __SSE2__
|
|
if (std_caps & (1<<26)) {
|
|
Sys_Printf(" SSE2");
|
|
i |= CPUID_SSE2;
|
|
}
|
|
#endif
|
|
#ifdef __SSE3__
|
|
if (ecx & 1) {
|
|
Sys_Printf(" SSE3");
|
|
i |= CPUID_SSE3;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
cpuid(0x80000000, max_ext_level, ebx, ecx, edx);
|
|
|
|
if (max_ext_level >= 0x80000001) {
|
|
cpuid(0x80000001, eax, ebx, ecx, ext_caps);
|
|
|
|
#ifdef __3dNOW__
|
|
if (ext_caps & (1U<<31)) {
|
|
Sys_Printf(" 3DNOW");
|
|
i |= CPUID_3DNOW;
|
|
}
|
|
#endif
|
|
#ifdef __MMX__
|
|
if (ext_caps & (1<<23)) {
|
|
if (!(i & CPUID_MMX))
|
|
Sys_Printf(" MMX");
|
|
i |= CPUID_MMX;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Sys_Printf("\n");
|
|
|
|
return i;
|
|
}
|
|
#else
|
|
int Sys_GetProcessorId( void ) {
|
|
return CPUID_GENERIC;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===============
|
|
Sys_GetProcessorString
|
|
===============
|
|
*/
|
|
const char *Sys_GetProcessorString( void ) {
|
|
return cpustring;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Sys_FPU_EnableExceptions
|
|
===============
|
|
*/
|
|
void Sys_FPU_EnableExceptions( int exceptions ) {
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Sys_FPE_handler
|
|
===============
|
|
*/
|
|
void Sys_FPE_handler( int signum, siginfo_t *info, void *context ) {
|
|
assert( signum == SIGFPE );
|
|
Sys_Printf( "FPE\n" );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Sys_GetClockticks
|
|
===============
|
|
*/
|
|
double Sys_GetClockTicks( void ) {
|
|
#if defined( __i386__ )
|
|
unsigned long lo, hi;
|
|
|
|
__asm__ __volatile__ (
|
|
"push %%ebx\n" \
|
|
"xor %%eax,%%eax\n" \
|
|
"cpuid\n" \
|
|
"rdtsc\n" \
|
|
"mov %%eax,%0\n" \
|
|
"mov %%edx,%1\n" \
|
|
"pop %%ebx\n"
|
|
: "=r" (lo), "=r" (hi) );
|
|
return (double) lo + (double) 0xFFFFFFFF * hi;
|
|
#else
|
|
return 0.0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
===============
|
|
MeasureClockTicks
|
|
===============
|
|
*/
|
|
double MeasureClockTicks( void ) {
|
|
double t0, t1;
|
|
|
|
t0 = Sys_GetClockTicks( );
|
|
Sys_Sleep( 1000 );
|
|
t1 = Sys_GetClockTicks( );
|
|
return t1 - t0;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Sys_ClockTicksPerSecond
|
|
===============
|
|
*/
|
|
double Sys_ClockTicksPerSecond(void) {
|
|
static bool init = false;
|
|
static double ret;
|
|
|
|
int fd, len, pos, end;
|
|
char buf[ 4096 ];
|
|
|
|
if ( init ) {
|
|
return ret;
|
|
}
|
|
|
|
fd = open( "/proc/cpuinfo", O_RDONLY );
|
|
if ( fd == -1 ) {
|
|
common->Printf( "couldn't read /proc/cpuinfo\n" );
|
|
ret = MeasureClockTicks();
|
|
init = true;
|
|
common->Printf( "measured CPU frequency: %g MHz\n", ret / 1000000.0 );
|
|
return ret;
|
|
}
|
|
len = read( fd, buf, 4096 );
|
|
close( fd );
|
|
pos = 0;
|
|
while ( pos < len ) {
|
|
if ( !idStr::Cmpn( buf + pos, "cpu MHz", 7 ) ) {
|
|
pos = strchr( buf + pos, ':' ) - buf + 2;
|
|
end = strchr( buf + pos, '\n' ) - buf;
|
|
if ( pos < len && end < len ) {
|
|
buf[end] = '\0';
|
|
ret = atof( buf + pos );
|
|
} else {
|
|
common->Printf( "failed parsing /proc/cpuinfo\n" );
|
|
ret = MeasureClockTicks();
|
|
init = true;
|
|
common->Printf( "measured CPU frequency: %g MHz\n", ret / 1000000.0 );
|
|
return ret;
|
|
}
|
|
common->Printf( "/proc/cpuinfo CPU frequency: %g MHz\n", ret );
|
|
ret *= 1000000;
|
|
init = true;
|
|
return ret;
|
|
}
|
|
pos = strchr( buf + pos, '\n' ) - buf + 1;
|
|
}
|
|
common->Printf( "failed parsing /proc/cpuinfo\n" );
|
|
ret = MeasureClockTicks();
|
|
init = true;
|
|
common->Printf( "measured CPU frequency: %g MHz\n", ret / 1000000.0 );
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Sys_GetSystemRam
|
|
returns in megabytes
|
|
================
|
|
*/
|
|
int Sys_GetSystemRam( void ) {
|
|
long count, page_size;
|
|
int mb;
|
|
|
|
count = sysconf( _SC_PHYS_PAGES );
|
|
if ( count == -1 ) {
|
|
common->Printf( "GetSystemRam: sysconf _SC_PHYS_PAGES failed\n" );
|
|
return 512;
|
|
}
|
|
page_size = sysconf( _SC_PAGE_SIZE );
|
|
if ( page_size == -1 ) {
|
|
common->Printf( "GetSystemRam: sysconf _SC_PAGE_SIZE failed\n" );
|
|
return 512;
|
|
}
|
|
mb= (int)( (double)count * (double)page_size / ( 1024 * 1024 ) );
|
|
// round to the nearest 16Mb
|
|
mb = ( mb + 8 ) & ~15;
|
|
return mb;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Sys_DoStartProcess
|
|
if we don't fork, this function never returns
|
|
the no-fork lets you keep the terminal when you're about to spawn an installer
|
|
|
|
if the command contains spaces, system() is used. Otherwise the more straightforward execl ( system() blows though )
|
|
==================
|
|
*/
|
|
void Sys_DoStartProcess( const char *exeName, bool dofork ) {
|
|
bool use_system = false;
|
|
if ( strchr( exeName, ' ' ) ) {
|
|
use_system = true;
|
|
} else {
|
|
// set exec rights when it's about a single file to execute
|
|
struct stat buf;
|
|
if ( stat( exeName, &buf ) == -1 ) {
|
|
printf( "stat %s failed: %s\n", exeName, strerror( errno ) );
|
|
} else {
|
|
if ( chmod( exeName, buf.st_mode | S_IXUSR ) == -1 ) {
|
|
printf( "cmod +x %s failed: %s\n", exeName, strerror( errno ) );
|
|
}
|
|
}
|
|
}
|
|
if ( dofork ) {
|
|
switch ( fork() ) {
|
|
case -1:
|
|
printf( "fork failed: %s\n", strerror( errno ) );
|
|
break;
|
|
case 0:
|
|
if ( use_system ) {
|
|
printf( "system %s\n", exeName );
|
|
if (system( exeName ) == -1)
|
|
printf( "system failed: %s\n", strerror( errno ) );
|
|
_exit( 0 );
|
|
} else {
|
|
printf( "execl %s\n", exeName );
|
|
execl( exeName, exeName, NULL );
|
|
printf( "execl failed: %s\n", strerror( errno ) );
|
|
_exit( -1 );
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
if ( use_system ) {
|
|
printf( "system %s\n", exeName );
|
|
if (system( exeName ) == -1)
|
|
printf( "system failed: %s\n", strerror( errno ) );
|
|
else
|
|
sleep( 1 ); // on some systems I've seen that starting the new process and exiting this one should not be too close
|
|
} else {
|
|
printf( "execl %s\n", exeName );
|
|
execl( exeName, exeName, NULL );
|
|
printf( "execl failed: %s\n", strerror( errno ) );
|
|
}
|
|
// terminate
|
|
_exit( 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Sys_OpenURL
|
|
=================
|
|
*/
|
|
void idSysLocal::OpenURL( const char *url, bool quit ) {
|
|
const char *script_path;
|
|
idFile *script_file;
|
|
char cmdline[ 1024 ];
|
|
|
|
static bool quit_spamguard = false;
|
|
|
|
if ( quit_spamguard ) {
|
|
common->DPrintf( "Sys_OpenURL: already in a doexit sequence, ignoring %s\n", url );
|
|
return;
|
|
}
|
|
|
|
common->Printf( "Open URL: %s\n", url );
|
|
// opening an URL on *nix can mean a lot of things ..
|
|
// just spawn a script instead of deciding for the user :-)
|
|
|
|
// look in the savepath first, then in the basepath
|
|
script_path = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_savepath" ), "", "openurl.sh" );
|
|
script_file = fileSystem->OpenExplicitFileRead( script_path );
|
|
if ( !script_file ) {
|
|
script_path = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_basepath" ), "", "openurl.sh" );
|
|
script_file = fileSystem->OpenExplicitFileRead( script_path );
|
|
}
|
|
if ( !script_file ) {
|
|
common->Printf( "Can't find URL script 'openurl.sh' in either savepath or basepath\n" );
|
|
common->Printf( "OpenURL '%s' failed\n", url );
|
|
return;
|
|
}
|
|
fileSystem->CloseFile( script_file );
|
|
|
|
// if we are going to quit, only accept a single URL before quitting and spawning the script
|
|
if ( quit ) {
|
|
quit_spamguard = true;
|
|
}
|
|
|
|
common->Printf( "URL script: %s\n", script_path );
|
|
|
|
// StartProcess is going to execute a system() call with that - hence the &
|
|
idStr::snPrintf( cmdline, 1024, "%s '%s' &", script_path, url );
|
|
sys->StartProcess( cmdline, quit );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Sys_DoPreferences
|
|
==================
|
|
*/
|
|
void Sys_DoPreferences( void ) { }
|
|
|
|
/*
|
|
================
|
|
Sys_FPU_SetDAZ
|
|
================
|
|
*/
|
|
void Sys_FPU_SetDAZ( bool enable ) {
|
|
/*
|
|
DWORD dwData;
|
|
|
|
_asm {
|
|
movzx ecx, byte ptr enable
|
|
and ecx, 1
|
|
shl ecx, 6
|
|
STMXCSR dword ptr dwData
|
|
mov eax, dwData
|
|
and eax, ~(1<<6) // clear DAX bit
|
|
or eax, ecx // set the DAZ bit
|
|
mov dwData, eax
|
|
LDMXCSR dword ptr dwData
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
================
|
|
Sys_FPU_SetFTZ
|
|
================
|
|
*/
|
|
void Sys_FPU_SetFTZ( bool enable ) {
|
|
/*
|
|
DWORD dwData;
|
|
|
|
_asm {
|
|
movzx ecx, byte ptr enable
|
|
and ecx, 1
|
|
shl ecx, 15
|
|
STMXCSR dword ptr dwData
|
|
mov eax, dwData
|
|
and eax, ~(1<<15) // clear FTZ bit
|
|
or eax, ecx // set the FTZ bit
|
|
mov dwData, eax
|
|
LDMXCSR dword ptr dwData
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
===============
|
|
mem consistency stuff
|
|
===============
|
|
*/
|
|
|
|
#ifdef ID_MCHECK
|
|
|
|
const char *mcheckstrings[] = {
|
|
"MCHECK_DISABLED",
|
|
"MCHECK_OK",
|
|
"MCHECK_FREE", // block freed twice
|
|
"MCHECK_HEAD", // memory before the block was clobbered
|
|
"MCHECK_TAIL" // memory after the block was clobbered
|
|
};
|
|
|
|
void abrt_func( mcheck_status status ) {
|
|
Sys_Printf( "memory consistency failure: %s\n", mcheckstrings[ status + 1 ] );
|
|
Posix_SetExit( EXIT_FAILURE );
|
|
common->Quit();
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
===============
|
|
main
|
|
===============
|
|
*/
|
|
int main(int argc, const char **argv) {
|
|
#ifdef ID_MCHECK
|
|
// must have -lmcheck linkage
|
|
mcheck( abrt_func );
|
|
Sys_Printf( "memory consistency checking enabled\n" );
|
|
#endif
|
|
|
|
Posix_EarlyInit( );
|
|
|
|
if ( argc > 1 ) {
|
|
common->Init( argc-1, &argv[1], NULL );
|
|
} else {
|
|
common->Init( 0, NULL, NULL );
|
|
}
|
|
|
|
Posix_LateInit( );
|
|
|
|
while (1) {
|
|
common->Frame();
|
|
}
|
|
}
|