GameLOD.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: GameLOD.cpp ///////////////////////////////////////////////////////////
  24. //
  25. // Used to set detail levels of various game systems.
  26. //
  27. // Author: Mark Wilczynski, Sept 2002
  28. //
  29. //
  30. ///////////////////////////////////////////////////////////////////////////////
  31. #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
  32. #include "Common/GameLOD.h"
  33. #include "GameClient/TerrainVisual.h"
  34. #include "GameClient/GameClient.h"
  35. #include "Common/UserPreferences.h"
  36. #define DEFINE_PARTICLE_SYSTEM_NAMES
  37. #include "GameClient/ParticleSys.h"
  38. #ifdef _INTERNAL
  39. // for occasional debugging...
  40. //#pragma optimize("", off)
  41. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  42. #endif
  43. #define PROFILE_ERROR_LIMIT 0.94f //fraction of profiled result needed to get a match. Allows some room for error/fluctuation.
  44. //Hack to get access to a static method on the W3DDevice side. -MW
  45. extern Bool testMinimumRequirements(ChipsetType *videoChipType, CpuType *cpuType, Int *cpuFreq, Int *numRAM, Real *intBenchIndex, Real *floatBenchIndex, Real *memBenchIndex);
  46. GameLODManager *TheGameLODManager=NULL;
  47. static const FieldParse TheStaticGameLODFieldParseTable[] =
  48. {
  49. { "MinimumFPS", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_minFPS)},
  50. { "MinimumProcessorFps", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_minProcessorFPS)},
  51. { "SampleCount2D", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_sampleCount2D ) },
  52. { "SampleCount3D", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_sampleCount3D ) },
  53. { "StreamCount", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_streamCount ) },
  54. { "MaxParticleCount", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxParticleCount ) },
  55. { "UseShadowVolumes", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useShadowVolumes ) },
  56. { "UseShadowDecals", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useShadowDecals ) },
  57. { "UseCloudMap", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useCloudMap ) },
  58. { "UseLightMap", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useLightMap ) },
  59. { "ShowSoftWaterEdge", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_showSoftWaterEdge ) },
  60. { "MaxTankTrackEdges", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxTankTrackEdges) },
  61. { "MaxTankTrackOpaqueEdges", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxTankTrackOpaqueEdges) },
  62. { "MaxTankTrackFadeDelay", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_maxTankTrackFadeDelay) },
  63. { "UseBuildupScaffolds", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useBuildupScaffolds ) },
  64. { "UseTreeSway", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useTreeSway ) },
  65. { "UseEmissiveNightMaterials", INI::parseBool, NULL, offsetof( StaticGameLODInfo, m_useEmissiveNightMaterials ) },
  66. { "TextureReductionFactor", INI::parseInt, NULL, offsetof( StaticGameLODInfo, m_textureReduction ) },
  67. };
  68. static const char *StaticGameLODNames[]=
  69. {
  70. "Low",
  71. "Medium",
  72. "High",
  73. "Custom"
  74. };
  75. StaticGameLODInfo::StaticGameLODInfo(void)
  76. {
  77. m_minFPS=0;
  78. m_minProcessorFPS=0;
  79. m_sampleCount2D=6;
  80. m_sampleCount3D=24;
  81. m_streamCount=2;
  82. m_maxParticleCount=2500;
  83. m_useShadowVolumes=TRUE;
  84. m_useShadowDecals=TRUE;
  85. m_useCloudMap=TRUE;
  86. m_useLightMap=TRUE;
  87. m_showSoftWaterEdge=TRUE;
  88. m_maxTankTrackEdges=100;
  89. m_maxTankTrackOpaqueEdges=25;
  90. m_maxTankTrackFadeDelay=300000;
  91. m_useBuildupScaffolds=TRUE;
  92. m_useTreeSway=TRUE;
  93. m_useEmissiveNightMaterials=TRUE;
  94. m_textureReduction = 0; //none
  95. m_useFpsLimit = TRUE;
  96. m_enableDynamicLOD = TRUE;
  97. m_useTrees = TRUE;
  98. }
  99. static const FieldParse TheDynamicGameLODFieldParseTable[] =
  100. {
  101. { "MinimumFPS", INI::parseInt, NULL, offsetof( DynamicGameLODInfo, m_minFPS)},
  102. { "ParticleSkipMask", INI::parseInt, NULL, offsetof( DynamicGameLODInfo, m_dynamicParticleSkipMask)},
  103. { "DebrisSkipMask", INI::parseInt, NULL, offsetof( DynamicGameLODInfo, m_dynamicDebrisSkipMask)},
  104. { "SlowDeathScale", INI::parseReal, NULL, offsetof( DynamicGameLODInfo, m_slowDeathScale)},
  105. { "MinParticlePriority", INI::parseIndexList, ParticlePriorityNames, offsetof( DynamicGameLODInfo, m_minDynamicParticlePriority)},
  106. { "MinParticleSkipPriority", INI::parseIndexList, ParticlePriorityNames, offsetof( DynamicGameLODInfo, m_minDynamicParticleSkipPriority)},
  107. };
  108. static const char *DynamicGameLODNames[]=
  109. {
  110. "Low",
  111. "Medium",
  112. "High",
  113. "VeryHigh"
  114. };
  115. DynamicGameLODInfo::DynamicGameLODInfo(void)
  116. {
  117. m_minFPS=0;
  118. m_dynamicParticleSkipMask=0;
  119. m_dynamicDebrisSkipMask=0;
  120. m_slowDeathScale=1.0f;
  121. m_minDynamicParticlePriority = PARTICLE_PRIORITY_LOWEST;
  122. m_minDynamicParticleSkipPriority = PARTICLE_PRIORITY_LOWEST;
  123. };
  124. //Keep this in sync with enum in GameLOD.h
  125. static char *CPUNames[] =
  126. {
  127. "XX","P3", "P4","K7", NULL
  128. };
  129. //Keep this in sync with enum in GameLOD.h
  130. static char *VideoNames[] =
  131. {
  132. "XX","V2","V3","V4","V5","TNT","TNT2","GF2","R100","PS11","GF3","GF4","PS14","R200","PS20","R300", NULL
  133. };
  134. void parseReallyLowMHz(INI* ini)
  135. {
  136. Int mhz;
  137. INI::parseInt(ini,NULL,&mhz,NULL);
  138. if (TheGameLODManager)
  139. {
  140. TheGameLODManager->setReallyLowMHz(mhz);
  141. }
  142. }
  143. void INI::parseBenchProfile( INI* ini)
  144. {
  145. if( TheGameLODManager )
  146. {
  147. BenchProfile *preset = TheGameLODManager->newBenchProfile();
  148. if (preset)
  149. {
  150. INI::parseIndexList(ini,NULL,&preset->m_cpuType,CPUNames);
  151. INI::parseInt(ini,NULL,&preset->m_mhz,NULL);
  152. INI::parseReal(ini,NULL,&preset->m_intBenchIndex,NULL);
  153. INI::parseReal(ini,NULL,&preset->m_floatBenchIndex,NULL);
  154. INI::parseReal(ini,NULL,&preset->m_memBenchIndex,NULL);
  155. }
  156. }
  157. }
  158. /**Parse a description of all the LOD settings for a given detail level*/
  159. void INI::parseLODPreset( INI* ini )
  160. {
  161. const char *c;
  162. AsciiString name;
  163. // read the name
  164. c = ini->getNextToken();
  165. name.set( c ); //name of detail level - low, medium, high
  166. if( TheGameLODManager )
  167. {
  168. StaticGameLODLevel index = (StaticGameLODLevel)TheGameLODManager->getStaticGameLODIndex(name);
  169. if (index != STATIC_GAME_LOD_UNKNOWN)
  170. {
  171. LODPresetInfo *preset = TheGameLODManager->newLODPreset(index);
  172. if (preset)
  173. {
  174. INI::parseIndexList(ini,NULL,&preset->m_cpuType,CPUNames);
  175. INI::parseInt(ini,NULL,&preset->m_mhz,NULL);
  176. INI::parseIndexList(ini,NULL,&preset->m_videoType,VideoNames);
  177. INI::parseInt(ini,NULL,&preset->m_memory,NULL);
  178. }
  179. }
  180. }
  181. }
  182. GameLODManager::GameLODManager(void)
  183. {
  184. m_currentStaticLOD = STATIC_GAME_LOD_UNKNOWN;
  185. m_currentDynamicLOD = DYNAMIC_GAME_LOD_HIGH;
  186. m_numParticleGenerations=0;
  187. m_dynamicParticleSkipMask=0;
  188. m_numDebrisGenerations=0;
  189. m_dynamicDebrisSkipMask=0;
  190. m_videoPassed=false;
  191. m_cpuPassed=false;
  192. m_memPassed=false;
  193. m_slowDeathScale=1.0f;
  194. m_idealDetailLevel = STATIC_GAME_LOD_UNKNOWN;
  195. m_videoChipType = DC_MAX;
  196. m_cpuType = XX;
  197. m_numRAM=0;
  198. m_cpuFreq=0;
  199. m_intBenchIndex=0;
  200. m_floatBenchIndex=0;
  201. m_memBenchIndex=0;
  202. m_compositeBenchIndex=0;
  203. m_numBenchProfiles=0;
  204. m_currentTextureReduction=0;
  205. m_reallyLowMHz = 400;
  206. for (Int i=0; i<STATIC_GAME_LOD_CUSTOM; i++)
  207. m_numLevelPresets[i]=0;
  208. };
  209. GameLODManager::~GameLODManager()
  210. {
  211. }
  212. BenchProfile *GameLODManager::newBenchProfile(void)
  213. {
  214. if (m_numBenchProfiles < MAX_BENCH_PROFILES)
  215. {
  216. m_numBenchProfiles++;
  217. return &m_benchProfiles[m_numBenchProfiles-1];
  218. }
  219. DEBUG_CRASH(( "GameLODManager::newBenchProfile - Too many profiles defined\n"));
  220. return NULL;
  221. }
  222. LODPresetInfo *GameLODManager::newLODPreset(StaticGameLODLevel index)
  223. {
  224. if (m_numLevelPresets[index] < MAX_LOD_PRESETS_PER_LEVEL)
  225. {
  226. m_numLevelPresets[index]++;
  227. return &m_lodPresets[index][m_numLevelPresets[index]-1];
  228. }
  229. DEBUG_CRASH(( "GameLODManager::newLODPreset - Too many presets defined for '%s'\n", TheGameLODManager->getStaticGameLODLevelName(index)));
  230. return NULL;
  231. }
  232. void GameLODManager::init(void)
  233. {
  234. INI ini;
  235. //Get Presets for each LOD level.
  236. ini.load( AsciiString( "Data\\INI\\GameLOD.ini" ), INI_LOAD_OVERWRITE, NULL );
  237. //Get presets for each known hardware configuration
  238. ini.load( AsciiString( "Data\\INI\\GameLODPresets.ini"), INI_LOAD_OVERWRITE, NULL);
  239. //Get Presets for custom LOD level by pulling them out of initial globaldata (which should
  240. //have all settings already applied).
  241. refreshCustomStaticLODLevel();
  242. //Override with user preferences
  243. OptionPreferences optionPref;
  244. StaticGameLODLevel userSetDetail=(StaticGameLODLevel)optionPref.getStaticGameDetail();
  245. m_idealDetailLevel=(StaticGameLODLevel)optionPref.getIdealStaticGameDetail();
  246. //always get this data in case we need it later.
  247. testMinimumRequirements(NULL,&m_cpuType,&m_cpuFreq,&m_numRAM,NULL,NULL,NULL);
  248. if ((Real)(m_numRAM)/(Real)(256*1024*1024) >= PROFILE_ERROR_LIMIT)
  249. m_memPassed=TRUE; //check if they have at least 256 MB
  250. if (m_idealDetailLevel == STATIC_GAME_LOD_UNKNOWN || TheGlobalData->m_forceBenchmark)
  251. {
  252. if (m_cpuType == XX || TheGlobalData->m_forceBenchmark)
  253. {
  254. //need to run the benchmark
  255. testMinimumRequirements(NULL,NULL,NULL,NULL,&m_intBenchIndex,&m_floatBenchIndex,&m_memBenchIndex);
  256. if (TheGlobalData->m_forceBenchmark)
  257. { //we want to see the numbers. So dump them to a logfile.
  258. FILE *fp=fopen("Benchmark.txt","w");
  259. if (fp)
  260. {
  261. fprintf(fp,"BenchProfile = %s %d %f %f %f", CPUNames[m_cpuType], m_cpuFreq, m_intBenchIndex, m_floatBenchIndex, m_memBenchIndex);
  262. fclose(fp);
  263. }
  264. }
  265. m_compositeBenchIndex = m_intBenchIndex + m_floatBenchIndex; ///@todo: Need to scale these based on our apps usage of int/float/mem ops.
  266. StaticGameLODLevel currentLevel=STATIC_GAME_LOD_LOW;
  267. BenchProfile *prof=m_benchProfiles;
  268. m_cpuType = P3; //assume lowest spec.
  269. m_cpuFreq = 1000; //assume lowest spec.
  270. for (Int k=0; k<m_numBenchProfiles; k++)
  271. {
  272. //Check if we're within 5% of the performance of this cpu profile.
  273. if (m_intBenchIndex/prof->m_intBenchIndex >= PROFILE_ERROR_LIMIT && m_floatBenchIndex/prof->m_floatBenchIndex >= PROFILE_ERROR_LIMIT && m_memBenchIndex/prof->m_memBenchIndex >= PROFILE_ERROR_LIMIT)
  274. {
  275. for (Int i=STATIC_GAME_LOD_HIGH; i >= STATIC_GAME_LOD_LOW; i--)
  276. {
  277. LODPresetInfo *preset=&m_lodPresets[i][0]; //pointer to first preset at this LOD level.
  278. for (Int j=0; j<m_numLevelPresets[i]; j++)
  279. {
  280. if( prof->m_cpuType == preset->m_cpuType && ((Real)prof->m_mhz/(Real)preset->m_mhz >= PROFILE_ERROR_LIMIT))
  281. { currentLevel = (StaticGameLODLevel)i;
  282. m_cpuType = prof->m_cpuType;
  283. m_cpuFreq = prof->m_mhz;
  284. break;
  285. }
  286. preset++; //skip to next preset
  287. }
  288. if (currentLevel >= i)
  289. break; //we already found a higher level than the remaining presets so no need to keep searching.
  290. }
  291. }
  292. prof++;
  293. }
  294. } //finding equivalent CPU to unkown cpu.
  295. } //find data needed to determine m_idealDetailLevel
  296. if (userSetDetail == STATIC_GAME_LOD_CUSTOM)
  297. {
  298. TheWritableGlobalData->m_textureReductionFactor = optionPref.getTextureReduction();
  299. TheWritableGlobalData->m_useShadowVolumes = optionPref.get3DShadowsEnabled();
  300. TheWritableGlobalData->m_useShadowDecals = optionPref.get2DShadowsEnabled();
  301. TheWritableGlobalData->m_enableBehindBuildingMarkers = optionPref.getBuildingOcclusionEnabled();
  302. TheWritableGlobalData->m_maxParticleCount = optionPref.getParticleCap();
  303. TheWritableGlobalData->m_enableDynamicLOD = optionPref.getDynamicLODEnabled();
  304. TheWritableGlobalData->m_useFpsLimit = optionPref.getFPSLimitEnabled();
  305. TheWritableGlobalData->m_useLightMap = optionPref.getLightmapEnabled();
  306. TheWritableGlobalData->m_useCloudMap = optionPref.getCloudShadowsEnabled();
  307. TheWritableGlobalData->m_showSoftWaterEdge = optionPref.getSmoothWaterEnabled();
  308. TheWritableGlobalData->m_useDrawModuleLOD = optionPref.getExtraAnimationsDisabled();
  309. TheWritableGlobalData->m_useTreeSway = !TheWritableGlobalData->m_useDrawModuleLOD; //borrow same setting.
  310. TheWritableGlobalData->m_useTrees = optionPref.getTreesEnabled();
  311. }
  312. setStaticLODLevel(userSetDetail);
  313. }
  314. void GameLODManager::refreshCustomStaticLODLevel(void)
  315. {
  316. StaticGameLODInfo *lodInfo=&m_staticGameLODInfo[STATIC_GAME_LOD_CUSTOM];
  317. lodInfo->m_maxParticleCount=TheGlobalData->m_maxParticleCount;
  318. lodInfo->m_useShadowVolumes=TheGlobalData->m_useShadowVolumes;
  319. lodInfo->m_useShadowDecals=TheGlobalData->m_useShadowDecals;
  320. lodInfo->m_useCloudMap=TheGlobalData->m_useCloudMap;
  321. lodInfo->m_useLightMap=TheGlobalData->m_useLightMap;
  322. lodInfo->m_showSoftWaterEdge=TheGlobalData->m_showSoftWaterEdge;
  323. lodInfo->m_maxTankTrackEdges=TheGlobalData->m_maxTankTrackEdges;
  324. lodInfo->m_maxTankTrackOpaqueEdges=TheGlobalData->m_maxTankTrackOpaqueEdges;
  325. lodInfo->m_maxTankTrackFadeDelay=TheGlobalData->m_maxTankTrackFadeDelay;
  326. lodInfo->m_useBuildupScaffolds=!TheGlobalData->m_useDrawModuleLOD;
  327. lodInfo->m_useTreeSway=lodInfo->m_useBuildupScaffolds;// Borrow same setting. //TheGlobalData->m_useTreeSway;
  328. lodInfo->m_textureReduction=TheGlobalData->m_textureReductionFactor;
  329. lodInfo->m_useFpsLimit = TheGlobalData->m_useFpsLimit;
  330. lodInfo->m_enableDynamicLOD=TheGlobalData->m_enableDynamicLOD;
  331. lodInfo->m_useTrees = TheGlobalData->m_useTrees;
  332. }
  333. /**Convert LOD name to an index*/
  334. Int GameLODManager::getStaticGameLODIndex(AsciiString name)
  335. {
  336. for (Int i=0; i<STATIC_GAME_LOD_COUNT; ++i)
  337. {
  338. if (name.compareNoCase(StaticGameLODNames[i]) == 0)
  339. return i;
  340. }
  341. DEBUG_CRASH(( "GameLODManager::getGameLODIndex - Invalid LOD name '%s'\n", name.str() ));
  342. return STATIC_GAME_LOD_UNKNOWN;
  343. }
  344. /**Parse a description of all the LOD settings for a given detail level*/
  345. void INI::parseStaticGameLODDefinition( INI* ini )
  346. {
  347. const char *c;
  348. AsciiString name;
  349. // read the name
  350. c = ini->getNextToken();
  351. name.set( c );
  352. if( TheGameLODManager )
  353. {
  354. Int index = TheGameLODManager->getStaticGameLODIndex(name);
  355. if (index != STATIC_GAME_LOD_UNKNOWN)
  356. {
  357. StaticGameLODInfo *lodInfo = &(TheGameLODManager->m_staticGameLODInfo[index]);
  358. // parse the ini definition
  359. ini->initFromINI( lodInfo, TheStaticGameLODFieldParseTable );
  360. }
  361. }
  362. }
  363. /**Parse an LOD level*/
  364. void INI::parseStaticGameLODLevel( INI* ini, void * , void *store, const void*)
  365. {
  366. const char *tok=ini->getNextToken();
  367. for (Int i=0; i<STATIC_GAME_LOD_COUNT; i++)
  368. if( stricmp(tok, StaticGameLODNames[i]) == 0 )
  369. { *(StaticGameLODLevel*)store = (StaticGameLODLevel)i;
  370. return;
  371. }
  372. DEBUG_CRASH(("invalid GameLODLevel token %s -- expected LOW/MEDIUM/HIGH\n",tok));
  373. throw INI_INVALID_DATA;
  374. }
  375. const char *GameLODManager::getStaticGameLODLevelName(StaticGameLODLevel level)
  376. {
  377. return StaticGameLODNames[level];
  378. }
  379. /**Function which calculates the recommended LOD level for current hardware
  380. configuration.*/
  381. StaticGameLODLevel GameLODManager::findStaticLODLevel(void)
  382. {
  383. //Check if we have never done the test on current system
  384. if (m_idealDetailLevel == STATIC_GAME_LOD_UNKNOWN)
  385. {
  386. //search all our presets for matching hardware
  387. m_idealDetailLevel = STATIC_GAME_LOD_LOW;
  388. //get system configuration - only need vide chip type, got rest in ::init().
  389. testMinimumRequirements(&m_videoChipType,NULL,NULL,NULL,NULL,NULL,NULL);
  390. if (m_videoChipType == DC_UNKNOWN)
  391. m_videoChipType = DC_TNT2; //presume it's at least TNT2 level
  392. Int numMBRam=m_numRAM/(1024*1024);
  393. for (Int i=STATIC_GAME_LOD_HIGH; i >= STATIC_GAME_LOD_LOW; i--)
  394. {
  395. LODPresetInfo *preset=&m_lodPresets[i][0]; //pointer to first preset at this LOD level.
  396. for (Int j=0; j<m_numLevelPresets[i]; j++)
  397. {
  398. if( m_cpuType == preset->m_cpuType &&
  399. ((Real)m_cpuFreq/(Real)preset->m_mhz >= PROFILE_ERROR_LIMIT) &&//make sure we're within 5% or higher
  400. m_videoChipType >= preset->m_videoType &&
  401. ((Real)numMBRam/(Real)preset->m_memory >= PROFILE_ERROR_LIMIT)
  402. )
  403. { m_idealDetailLevel = (StaticGameLODLevel)i;
  404. break;
  405. }
  406. preset++; //skip to next preset
  407. }
  408. if (m_idealDetailLevel >= i)
  409. break; //we already found a higher level than the remaining presets so no need to keep searching.
  410. }
  411. //Save ideal detail level for future usage
  412. OptionPreferences optionPref;
  413. optionPref["IdealStaticGameLOD"] = getStaticGameLODLevelName(m_idealDetailLevel);
  414. if (getStaticLODLevel() == STATIC_GAME_LOD_UNKNOWN) //save for future usage.
  415. optionPref["StaticGameLOD"] = getStaticGameLODLevelName(m_idealDetailLevel);
  416. optionPref.write();
  417. }
  418. return m_idealDetailLevel;
  419. }
  420. /**Set all game systems to match the desired LOD level.*/
  421. Bool GameLODManager::setStaticLODLevel(StaticGameLODLevel level)
  422. {
  423. if (!TheGlobalData->m_enableStaticLOD)
  424. { m_currentStaticLOD = STATIC_GAME_LOD_CUSTOM;
  425. return FALSE;
  426. }
  427. if (level == STATIC_GAME_LOD_UNKNOWN || (level != STATIC_GAME_LOD_CUSTOM && m_currentStaticLOD == level))
  428. return FALSE; //level is already applied. Custom levels are always applied since random options could change.
  429. applyStaticLODLevel(level);
  430. m_currentStaticLOD = level;
  431. return TRUE;
  432. }
  433. void GameLODManager::applyStaticLODLevel(StaticGameLODLevel level)
  434. {
  435. ///@todo: Still need to implement these settings:
  436. // m_sampleCount2D=6;
  437. // m_sampleCount3D=24;
  438. // m_streamCount=2;
  439. // m_useEmissiveNightMaterials=TRUE;
  440. //save previous info for this level since it may be overwritten by refreshCustomStaticLODLevel().
  441. StaticGameLODInfo prevLodBackup;
  442. if (m_currentStaticLOD != STATIC_GAME_LOD_UNKNOWN)
  443. prevLodBackup=m_staticGameLODInfo[m_currentStaticLOD];
  444. if (level == STATIC_GAME_LOD_CUSTOM)
  445. refreshCustomStaticLODLevel(); //store current settings into custom preset
  446. StaticGameLODInfo *lodInfo=&m_staticGameLODInfo[level];
  447. StaticGameLODInfo *prevLodInfo=&prevLodBackup;
  448. Int requestedTextureReduction = 0;
  449. Bool requestedTrees = m_memPassed; //only use trees if memory requirement passed.
  450. if (level == STATIC_GAME_LOD_CUSTOM)
  451. { requestedTextureReduction = lodInfo->m_textureReduction;
  452. requestedTrees = lodInfo->m_useTrees;
  453. }
  454. else
  455. if (level >= STATIC_GAME_LOD_LOW)
  456. { //normal non-custom level gets texture reduction based on recommendation
  457. requestedTextureReduction = getRecommendedTextureReduction();
  458. }
  459. if (TheGlobalData)
  460. {
  461. TheWritableGlobalData->m_maxParticleCount=lodInfo->m_maxParticleCount;
  462. TheWritableGlobalData->m_useShadowVolumes=lodInfo->m_useShadowVolumes;
  463. TheWritableGlobalData->m_useShadowDecals=lodInfo->m_useShadowDecals;
  464. //Check if texture resolution changed. No need to apply when current is unknown because display will do it
  465. if (requestedTextureReduction != m_currentTextureReduction)
  466. {
  467. TheWritableGlobalData->m_textureReductionFactor = requestedTextureReduction;
  468. if (TheGameClient)
  469. TheGameClient->adjustLOD(0); //apply the new setting stored in globaldata
  470. }
  471. //Check if shadow state changed
  472. if (m_currentStaticLOD == STATIC_GAME_LOD_UNKNOWN ||
  473. lodInfo->m_useShadowVolumes != prevLodInfo->m_useShadowVolumes ||
  474. lodInfo->m_useShadowDecals != prevLodInfo->m_useShadowDecals)
  475. {
  476. if (TheGameClient)
  477. {
  478. TheGameClient->releaseShadows(); //free all shadows
  479. TheGameClient->allocateShadows(); //allocate those shadows that are enabled.
  480. }
  481. }
  482. TheWritableGlobalData->m_useCloudMap=lodInfo->m_useCloudMap;
  483. TheWritableGlobalData->m_useLightMap=lodInfo->m_useLightMap;
  484. TheWritableGlobalData->m_showSoftWaterEdge=lodInfo->m_showSoftWaterEdge;
  485. //Check if shoreline blending mode has changed
  486. if (m_currentStaticLOD == STATIC_GAME_LOD_UNKNOWN || lodInfo->m_showSoftWaterEdge != prevLodInfo->m_showSoftWaterEdge)
  487. {
  488. if (TheTerrainVisual)
  489. TheTerrainVisual->setShoreLineDetail();
  490. }
  491. TheWritableGlobalData->m_maxTankTrackEdges=lodInfo->m_maxTankTrackEdges;
  492. TheWritableGlobalData->m_maxTankTrackOpaqueEdges=lodInfo->m_maxTankTrackOpaqueEdges;
  493. TheWritableGlobalData->m_maxTankTrackFadeDelay=lodInfo->m_maxTankTrackFadeDelay;
  494. TheWritableGlobalData->m_useTreeSway=lodInfo->m_useTreeSway;
  495. TheWritableGlobalData->m_useDrawModuleLOD=!lodInfo->m_useBuildupScaffolds;
  496. TheWritableGlobalData->m_enableDynamicLOD = lodInfo->m_enableDynamicLOD;
  497. TheWritableGlobalData->m_useFpsLimit = lodInfo->m_useFpsLimit;
  498. TheWritableGlobalData->m_useTrees = requestedTrees;
  499. }
  500. if (!m_memPassed || isReallyLowMHz()) {
  501. TheWritableGlobalData->m_shellMapOn = false;
  502. }
  503. if (TheTerrainVisual)
  504. TheTerrainVisual->setTerrainTracksDetail();
  505. }
  506. /**Parse a description of all the LOD settings for a given detail level*/
  507. void INI::parseDynamicGameLODDefinition( INI* ini )
  508. {
  509. const char *c;
  510. AsciiString name;
  511. // read the name
  512. c = ini->getNextToken();
  513. name.set( c );
  514. if( TheGameLODManager )
  515. {
  516. Int index = TheGameLODManager->getDynamicGameLODIndex(name);
  517. if (index != DYNAMIC_GAME_LOD_UNKNOWN)
  518. {
  519. DynamicGameLODInfo *lodInfo = &(TheGameLODManager->m_dynamicGameLODInfo[index]);
  520. // parse the ini weapon definition
  521. ini->initFromINI( lodInfo, TheDynamicGameLODFieldParseTable );
  522. }
  523. }
  524. }
  525. /**Parse an LOD level*/
  526. void INI::parseDynamicGameLODLevel( INI* ini, void * , void *store, const void*)
  527. {
  528. const char *tok=ini->getNextToken();
  529. for (Int i=0; i<DYNAMIC_GAME_LOD_COUNT; i++)
  530. if( stricmp(tok, DynamicGameLODNames[i]) == 0 )
  531. { *(DynamicGameLODLevel*)store = (DynamicGameLODLevel)i;
  532. return;
  533. }
  534. DEBUG_CRASH(("invalid GameLODLevel token %s -- expected LOW/MEDIUM/HIGH\n",tok));
  535. throw INI_INVALID_DATA;
  536. }
  537. /**Convert LOD name to an index*/
  538. Int GameLODManager::getDynamicGameLODIndex(AsciiString name)
  539. {
  540. for (Int i=0; i<DYNAMIC_GAME_LOD_COUNT; ++i)
  541. {
  542. if (name.compareNoCase(DynamicGameLODNames[i]) == 0)
  543. return i;
  544. }
  545. DEBUG_CRASH(( "GameLODManager::getGameLODIndex - Invalid LOD name '%s'\n", name.str() ));
  546. return STATIC_GAME_LOD_UNKNOWN;
  547. }
  548. const char *GameLODManager::getDynamicGameLODLevelName(DynamicGameLODLevel level)
  549. {
  550. return DynamicGameLODNames[level];
  551. }
  552. /**Given an average fps, return the optimal dynamic LOD level that matches this fps.*/
  553. DynamicGameLODLevel GameLODManager::findDynamicLODLevel(Real averageFPS)
  554. {
  555. Int ifps=(Int)(averageFPS); //convert to integer.
  556. for (Int i=DYNAMIC_GAME_LOD_VERY_HIGH; i>=DYNAMIC_GAME_LOD_LOW; i--)
  557. { //check which of the LOD levels matches our fps
  558. if (m_dynamicGameLODInfo[i].m_minFPS < ifps)
  559. return (DynamicGameLODLevel)i;
  560. }
  561. return DYNAMIC_GAME_LOD_LOW; //none of the low levels were slow enough so pick the lowest.
  562. }
  563. /**Set all game systems to match the desired LOD level.*/
  564. Bool GameLODManager::setDynamicLODLevel(DynamicGameLODLevel level)
  565. {
  566. if (level == DYNAMIC_GAME_LOD_UNKNOWN || m_currentDynamicLOD == level)
  567. return FALSE;
  568. m_currentDynamicLOD = level;
  569. applyDynamicLODLevel(level);
  570. return TRUE;
  571. }
  572. void GameLODManager::applyDynamicLODLevel(DynamicGameLODLevel level)
  573. {
  574. m_numParticleGenerations=0;
  575. m_dynamicParticleSkipMask=m_dynamicGameLODInfo[level].m_dynamicParticleSkipMask;
  576. m_numDebrisGenerations=0;
  577. m_dynamicDebrisSkipMask=m_dynamicGameLODInfo[level].m_dynamicDebrisSkipMask;
  578. m_slowDeathScale=m_dynamicGameLODInfo[level].m_slowDeathScale;
  579. m_minDynamicParticlePriority=m_dynamicGameLODInfo[level].m_minDynamicParticlePriority;
  580. m_minDynamicParticleSkipPriority=m_dynamicGameLODInfo[level].m_minDynamicParticleSkipPriority;
  581. }
  582. Int GameLODManager::getRecommendedTextureReduction(void)
  583. {
  584. if (m_idealDetailLevel == STATIC_GAME_LOD_UNKNOWN)
  585. findStaticLODLevel(); //it was never tested, so test now.
  586. if (!m_memPassed) //if they have < 256 MB, force them to low res textures.
  587. return m_staticGameLODInfo[STATIC_GAME_LOD_LOW].m_textureReduction;
  588. return m_staticGameLODInfo[m_idealDetailLevel].m_textureReduction;
  589. }
  590. Int GameLODManager::getLevelTextureReduction(StaticGameLODLevel level)
  591. {
  592. return m_staticGameLODInfo[level].m_textureReduction;
  593. }
  594. Bool GameLODManager::didMemPass( void )
  595. {
  596. return m_memPassed;
  597. }